-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #298 from xmidt-org/feature/credentials
Feature/credentials
- Loading branch information
Showing
16 changed files
with
751 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package basculehash | ||
|
||
import "context" | ||
|
||
// Credentials is a source of principals and their associated digests. A | ||
// credentials instance may be in-memory or a remote system. | ||
type Credentials interface { | ||
// Get returns the Digest associated with the given Principal. | ||
// This method returns false if the principal did not exist. | ||
Get(ctx context.Context, principal string) (d Digest, exists bool) | ||
|
||
// Set associates a principal with a Digest. If the principal already | ||
// exists, its digest is replaced. | ||
Set(ctx context.Context, principal string, d Digest) | ||
|
||
// Delete removes one or more principals from this set. | ||
Delete(ctx context.Context, principals ...string) | ||
|
||
// Update performs a bulk update of these credentials. Any existing | ||
// principals are replaced. | ||
Update(ctx context.Context, p Principals) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package basculehash | ||
|
||
import ( | ||
"context" | ||
|
||
"golang.org/x/crypto/bcrypt" | ||
) | ||
|
||
// CredentialsTestSuite runs a standard battery of tests against | ||
// a Credentials implementation. | ||
// | ||
// Tests of UnmarshalJSON need to be done in tests of concrete types | ||
// due to the way unmarshalling works in golang. | ||
type CredentialsTestSuite[C Credentials] struct { | ||
TestSuite | ||
|
||
// Implementations should supply SetupTest and SetupSubTest | ||
// methods that populate this member. Don't forget to call | ||
// TestSuite.SetupTest and TestSuite.SetupSubTest! | ||
credentials C | ||
|
||
testCtx context.Context | ||
hasher Hasher | ||
} | ||
|
||
// SetupSuite initializes a hasher and comparer to use when verifying | ||
// and creating digests. | ||
func (suite *CredentialsTestSuite[C]) SetupSuite() { | ||
suite.testCtx = context.Background() | ||
suite.hasher = Bcrypt{Cost: bcrypt.MinCost} | ||
} | ||
|
||
// exists asserts that a given principal exists with the given Digest. | ||
func (suite *CredentialsTestSuite[C]) exists(principal string, expected Digest) { | ||
d, ok := suite.credentials.Get(suite.testCtx, principal) | ||
suite.Require().True(ok) | ||
suite.Require().Equal(expected, d) | ||
} | ||
|
||
// notExists asserts that the given principal did not exist. | ||
func (suite *CredentialsTestSuite[C]) notExists(principal string) { | ||
d, ok := suite.credentials.Get(suite.testCtx, principal) | ||
suite.Require().False(ok) | ||
suite.Require().Empty(d) | ||
} | ||
|
||
// defaultHash creates a distinct hash of the suite plaintext for testing. | ||
func (suite *CredentialsTestSuite[C]) defaultHash() Digest { | ||
return suite.goodHash( | ||
suite.hasher.Hash( | ||
suite.plaintext, | ||
), | ||
) | ||
} | ||
|
||
func (suite *CredentialsTestSuite[C]) TestGetSetDelete() { | ||
suite.T().Log("delete from empty") | ||
suite.credentials.Delete(suite.testCtx, "joe") | ||
|
||
suite.T().Log("add") | ||
joeDigest := suite.defaultHash() | ||
suite.credentials.Set(suite.testCtx, "joe", joeDigest) | ||
suite.exists("joe", joeDigest) | ||
|
||
suite.T().Log("add another") | ||
fredDigest := suite.defaultHash() | ||
suite.credentials.Set(suite.testCtx, "fred", fredDigest) | ||
suite.exists("joe", joeDigest) | ||
suite.exists("fred", fredDigest) | ||
|
||
suite.T().Log("replace") | ||
newJoeDigest := suite.defaultHash() | ||
suite.Require().NotEqual(newJoeDigest, joeDigest) // hashes should always generate salt to make them distinct | ||
suite.credentials.Set(suite.testCtx, "joe", newJoeDigest) | ||
suite.exists("joe", newJoeDigest) | ||
suite.exists("fred", fredDigest) | ||
|
||
suite.T().Log("delete a principal") | ||
suite.credentials.Delete(suite.testCtx, "fred") | ||
suite.notExists("fred") | ||
suite.exists("joe", newJoeDigest) | ||
} | ||
|
||
func (suite *CredentialsTestSuite[C]) TestUpdate() { | ||
suite.credentials.Update(suite.testCtx, nil) | ||
|
||
joeDigest := suite.defaultHash() | ||
fredDigest := suite.defaultHash() | ||
suite.credentials.Update(suite.testCtx, Principals{ | ||
"joe": joeDigest, | ||
"fred": fredDigest, | ||
}) | ||
|
||
suite.exists("joe", joeDigest) | ||
suite.exists("fred", fredDigest) | ||
|
||
joeDigest = suite.defaultHash() | ||
moeDigest := suite.defaultHash() | ||
suite.credentials.Update(suite.testCtx, Principals{ | ||
"joe": joeDigest, | ||
"moe": moeDigest, | ||
}) | ||
|
||
suite.exists("joe", joeDigest) | ||
suite.exists("fred", fredDigest) | ||
suite.exists("moe", moeDigest) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-FileCopyrightText: 2024 Comcast Cable Communications Management, LLC | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package basculehash | ||
|
||
import "io" | ||
|
||
// Digest is the result of applying a Hasher to plaintext. | ||
// A digest must be valid UTF-8, preferably using the format | ||
// described by https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md. | ||
type Digest []byte | ||
|
||
// Copy returns a distinct copy of this digest. | ||
func (d Digest) Copy() Digest { | ||
clone := make(Digest, len(d)) | ||
copy(clone, d) | ||
return clone | ||
} | ||
|
||
// String returns this Digest as is, but cast as a string. | ||
func (d Digest) String() string { | ||
return string(d) | ||
} | ||
|
||
// MarshalText simply returns this Digest as a byte slice. This method ensures | ||
// that the digest is written as is instead of encoded as base64 or some other | ||
// encoding. | ||
func (d Digest) MarshalText() ([]byte, error) { | ||
return []byte(d), nil | ||
} | ||
|
||
// UnmarshalText uses the given text as is. | ||
func (d *Digest) UnmarshalText(text []byte) error { | ||
*d = text | ||
return nil | ||
} | ||
|
||
// WriteTo writes this digest to the given writer. | ||
func (d Digest) WriteTo(dst io.Writer) (int64, error) { | ||
c, err := dst.Write(d) | ||
return int64(c), err | ||
} |
Oops, something went wrong.