2016-09-18 00:44:53 +00:00
|
|
|
// Package conformance provides conformance tests for storage implementations.
|
|
|
|
package conformance
|
2016-07-25 20:00:28 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"reflect"
|
2016-11-17 22:50:58 +00:00
|
|
|
"sort"
|
2016-07-25 20:00:28 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2020-11-03 19:50:09 +00:00
|
|
|
"github.com/kylelemons/godebug/pretty"
|
2016-10-05 23:03:53 +00:00
|
|
|
"golang.org/x/crypto/bcrypt"
|
2020-11-03 19:50:09 +00:00
|
|
|
jose "gopkg.in/square/go-jose.v2"
|
2016-10-05 23:03:53 +00:00
|
|
|
|
2018-09-03 06:44:44 +00:00
|
|
|
"github.com/dexidp/dex/storage"
|
2016-07-25 20:00:28 +00:00
|
|
|
)
|
|
|
|
|
2016-09-19 15:41:54 +00:00
|
|
|
// ensure that values being tested on never expire.
|
|
|
|
var neverExpire = time.Now().UTC().Add(time.Hour * 24 * 365 * 100)
|
2016-07-25 20:00:28 +00:00
|
|
|
|
2016-10-26 20:04:50 +00:00
|
|
|
type subTest struct {
|
|
|
|
name string
|
|
|
|
run func(t *testing.T, s storage.Storage)
|
|
|
|
}
|
|
|
|
|
|
|
|
func runTests(t *testing.T, newStorage func() storage.Storage, tests []subTest) {
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
s := newStorage()
|
|
|
|
test.run(t, s)
|
|
|
|
s.Close()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-13 01:47:15 +00:00
|
|
|
// RunTests runs a set of conformance tests against a storage. newStorage should
|
|
|
|
// return an initialized but empty storage. The storage will be closed at the
|
|
|
|
// end of each test run.
|
|
|
|
func RunTests(t *testing.T, newStorage func() storage.Storage) {
|
2016-10-26 20:04:50 +00:00
|
|
|
runTests(t, newStorage, []subTest{
|
2016-09-19 15:41:54 +00:00
|
|
|
{"AuthCodeCRUD", testAuthCodeCRUD},
|
|
|
|
{"AuthRequestCRUD", testAuthRequestCRUD},
|
|
|
|
{"ClientCRUD", testClientCRUD},
|
|
|
|
{"RefreshTokenCRUD", testRefreshTokenCRUD},
|
2016-10-05 23:03:53 +00:00
|
|
|
{"PasswordCRUD", testPasswordCRUD},
|
2016-10-14 18:56:31 +00:00
|
|
|
{"KeysCRUD", testKeysCRUD},
|
2017-02-01 00:11:59 +00:00
|
|
|
{"OfflineSessionCRUD", testOfflineSessionCRUD},
|
2017-03-23 16:59:33 +00:00
|
|
|
{"ConnectorCRUD", testConnectorCRUD},
|
2016-10-13 01:47:15 +00:00
|
|
|
{"GarbageCollection", testGC},
|
2016-12-16 19:03:36 +00:00
|
|
|
{"TimezoneSupport", testTimezones},
|
2020-01-16 15:55:07 +00:00
|
|
|
{"DeviceRequestCRUD", testDeviceRequestCRUD},
|
|
|
|
{"DeviceTokenCRUD", testDeviceTokenCRUD},
|
2016-10-26 20:04:50 +00:00
|
|
|
})
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
2016-10-14 18:56:31 +00:00
|
|
|
func mustLoadJWK(b string) *jose.JSONWebKey {
|
|
|
|
var jwt jose.JSONWebKey
|
|
|
|
if err := jwt.UnmarshalJSON([]byte(b)); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return &jwt
|
|
|
|
}
|
|
|
|
|
2016-09-19 15:41:54 +00:00
|
|
|
func mustBeErrNotFound(t *testing.T, kind string, err error) {
|
|
|
|
switch {
|
|
|
|
case err == nil:
|
2016-12-13 20:23:16 +00:00
|
|
|
t.Errorf("deleting non-existent %s should return an error", kind)
|
2016-09-19 15:41:54 +00:00
|
|
|
case err != storage.ErrNotFound:
|
|
|
|
t.Errorf("deleting %s expected storage.ErrNotFound, got %v", kind, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-21 23:00:22 +00:00
|
|
|
func mustBeErrAlreadyExists(t *testing.T, kind string, err error) {
|
|
|
|
switch {
|
|
|
|
case err == nil:
|
|
|
|
t.Errorf("attempting to create an existing %s should return an error", kind)
|
|
|
|
case err != storage.ErrAlreadyExists:
|
|
|
|
t.Errorf("creating an existing %s expected storage.ErrAlreadyExists, got %v", kind, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-19 15:41:54 +00:00
|
|
|
func testAuthRequestCRUD(t *testing.T, s storage.Storage) {
|
PKCE implementation (#1784)
* Basic implementation of PKCE
Signed-off-by: Tadeusz Magura-Witkowski <tadeuszmw@gmail.com>
* @mfmarche on 24 Feb: when code_verifier is set, don't check client_secret
In PKCE flow, no client_secret is used, so the check for a valid client_secret
would always fail.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* @deric on 16 Jun: return invalid_grant when wrong code_verifier
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Enforce PKCE flow on /token when PKCE flow was started on /auth
Also dissallow PKCE on /token, when PKCE flow was not started on /auth
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* fixed error messages when mixed PKCE/no PKCE flow.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* server_test.go: Added PKCE error cases on /token endpoint
* Added test for invalid_grant, when wrong code_verifier is sent
* Added test for mixed PKCE / no PKCE auth flows.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* cleanup: extracted method checkErrorResponse and type TestDefinition
* fixed connector being overwritten
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* /token endpoint: skip client_secret verification only for grand type authorization_code with PKCE extension
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Allow "Authorization" header in CORS handlers
* Adds "Authorization" to the default CORS headers{"Accept", "Accept-Language", "Content-Language", "Origin"}
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Add "code_challenge_methods_supported" to discovery endpoint
discovery endpoint /dex/.well-known/openid-configuration
now has the following entry:
"code_challenge_methods_supported": [
"S256",
"plain"
]
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Updated tests (mixed-up comments), added a PKCE test
* @asoorm added test that checks if downgrade to "plain" on /token endpoint
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* remove redefinition of providedCodeVerifier, fixed spelling (#6)
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
Signed-off-by: Bernd Eckstein <HEllRZA@users.noreply.github.com>
* Rename struct CodeChallenge to PKCE
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* PKCE: Check clientSecret when available
In authorization_code flow with PKCE, allow empty client_secret on /auth and /token endpoints. But check the client_secret when it is given.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Enable PKCE with public: true
dex configuration public on staticClients now enables the following behavior in PKCE:
- Public: false, PKCE will always check client_secret. This means PKCE in it's natural form is disabled.
- Public: true, PKCE is enabled. It will only check client_secret if the client has sent one. But it allows the code flow if the client didn't sent one.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Redirect error on unsupported code_challenge_method
- Check for unsupported code_challenge_method after redirect uri is validated, and use newErr() to return the error.
- Add PKCE tests to oauth2_test.go
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Reverted go.mod and go.sum to the state of master
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Don't omit client secret check for PKCE
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Allow public clients (e.g. with PKCE) to have redirect URIs configured
Signed-off-by: Martin Heide <martin.heide@faro.com>
* Remove "Authorization" as Accepted Headers on CORS, small fixes
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Revert "Allow public clients (e.g. with PKCE) to have redirect URIs configured"
This reverts commit b6e297b78537dc44cd3e1374f0b4d34bf89404ac.
Signed-off-by: Martin Heide <martin.heide@faro.com>
* PKCE on client_secret client error message
* When connecting to the token endpoint with PKCE without client_secret, but the client is configured with a client_secret, generate a special error message.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Output info message when PKCE without client_secret used on confidential client
* removes the special error message
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* General missing/invalid client_secret message on token endpoint
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
Co-authored-by: Tadeusz Magura-Witkowski <tadeuszmw@gmail.com>
Co-authored-by: Martin Heide <martin.heide@faro.com>
Co-authored-by: M. Heide <66078329+heidemn-faro@users.noreply.github.com>
2020-10-26 10:33:40 +00:00
|
|
|
codeChallenge := storage.PKCE{
|
|
|
|
CodeChallenge: "code_challenge_test",
|
|
|
|
CodeChallengeMethod: "plain",
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
a1 := storage.AuthRequest{
|
2016-09-19 15:41:54 +00:00
|
|
|
ID: storage.NewID(),
|
2017-03-15 22:00:55 +00:00
|
|
|
ClientID: "client1",
|
2016-09-19 15:41:54 +00:00
|
|
|
ResponseTypes: []string{"code"},
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
RedirectURI: "https://localhost:80/callback",
|
|
|
|
Nonce: "foo",
|
|
|
|
State: "bar",
|
|
|
|
ForceApprovalPrompt: true,
|
|
|
|
LoggedIn: true,
|
|
|
|
Expiry: neverExpire,
|
|
|
|
ConnectorID: "ldap",
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "1",
|
|
|
|
Username: "jane",
|
|
|
|
Email: "jane.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a", "b"},
|
|
|
|
},
|
PKCE implementation (#1784)
* Basic implementation of PKCE
Signed-off-by: Tadeusz Magura-Witkowski <tadeuszmw@gmail.com>
* @mfmarche on 24 Feb: when code_verifier is set, don't check client_secret
In PKCE flow, no client_secret is used, so the check for a valid client_secret
would always fail.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* @deric on 16 Jun: return invalid_grant when wrong code_verifier
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Enforce PKCE flow on /token when PKCE flow was started on /auth
Also dissallow PKCE on /token, when PKCE flow was not started on /auth
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* fixed error messages when mixed PKCE/no PKCE flow.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* server_test.go: Added PKCE error cases on /token endpoint
* Added test for invalid_grant, when wrong code_verifier is sent
* Added test for mixed PKCE / no PKCE auth flows.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* cleanup: extracted method checkErrorResponse and type TestDefinition
* fixed connector being overwritten
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* /token endpoint: skip client_secret verification only for grand type authorization_code with PKCE extension
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Allow "Authorization" header in CORS handlers
* Adds "Authorization" to the default CORS headers{"Accept", "Accept-Language", "Content-Language", "Origin"}
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Add "code_challenge_methods_supported" to discovery endpoint
discovery endpoint /dex/.well-known/openid-configuration
now has the following entry:
"code_challenge_methods_supported": [
"S256",
"plain"
]
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Updated tests (mixed-up comments), added a PKCE test
* @asoorm added test that checks if downgrade to "plain" on /token endpoint
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* remove redefinition of providedCodeVerifier, fixed spelling (#6)
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
Signed-off-by: Bernd Eckstein <HEllRZA@users.noreply.github.com>
* Rename struct CodeChallenge to PKCE
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* PKCE: Check clientSecret when available
In authorization_code flow with PKCE, allow empty client_secret on /auth and /token endpoints. But check the client_secret when it is given.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Enable PKCE with public: true
dex configuration public on staticClients now enables the following behavior in PKCE:
- Public: false, PKCE will always check client_secret. This means PKCE in it's natural form is disabled.
- Public: true, PKCE is enabled. It will only check client_secret if the client has sent one. But it allows the code flow if the client didn't sent one.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Redirect error on unsupported code_challenge_method
- Check for unsupported code_challenge_method after redirect uri is validated, and use newErr() to return the error.
- Add PKCE tests to oauth2_test.go
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Reverted go.mod and go.sum to the state of master
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Don't omit client secret check for PKCE
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Allow public clients (e.g. with PKCE) to have redirect URIs configured
Signed-off-by: Martin Heide <martin.heide@faro.com>
* Remove "Authorization" as Accepted Headers on CORS, small fixes
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Revert "Allow public clients (e.g. with PKCE) to have redirect URIs configured"
This reverts commit b6e297b78537dc44cd3e1374f0b4d34bf89404ac.
Signed-off-by: Martin Heide <martin.heide@faro.com>
* PKCE on client_secret client error message
* When connecting to the token endpoint with PKCE without client_secret, but the client is configured with a client_secret, generate a special error message.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Output info message when PKCE without client_secret used on confidential client
* removes the special error message
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* General missing/invalid client_secret message on token endpoint
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
Co-authored-by: Tadeusz Magura-Witkowski <tadeuszmw@gmail.com>
Co-authored-by: Martin Heide <martin.heide@faro.com>
Co-authored-by: M. Heide <66078329+heidemn-faro@users.noreply.github.com>
2020-10-26 10:33:40 +00:00
|
|
|
PKCE: codeChallenge,
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
2016-08-03 04:57:36 +00:00
|
|
|
identity := storage.Claims{Email: "foobar"}
|
2016-07-25 20:00:28 +00:00
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.CreateAuthRequest(a1); err != nil {
|
2016-07-25 20:00:28 +00:00
|
|
|
t.Fatalf("failed creating auth request: %v", err)
|
|
|
|
}
|
2017-02-21 23:00:22 +00:00
|
|
|
|
|
|
|
// Attempt to create same AuthRequest twice.
|
2017-03-15 22:00:55 +00:00
|
|
|
err := s.CreateAuthRequest(a1)
|
2017-02-21 23:00:22 +00:00
|
|
|
mustBeErrAlreadyExists(t, "auth request", err)
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
a2 := storage.AuthRequest{
|
|
|
|
ID: storage.NewID(),
|
|
|
|
ClientID: "client2",
|
|
|
|
ResponseTypes: []string{"code"},
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
RedirectURI: "https://localhost:80/callback",
|
|
|
|
Nonce: "bar",
|
|
|
|
State: "foo",
|
|
|
|
ForceApprovalPrompt: true,
|
|
|
|
LoggedIn: true,
|
|
|
|
Expiry: neverExpire,
|
|
|
|
ConnectorID: "ldap",
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "2",
|
|
|
|
Username: "john",
|
|
|
|
Email: "john.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateAuthRequest(a2); err != nil {
|
|
|
|
t.Fatalf("failed creating auth request: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.UpdateAuthRequest(a1.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) {
|
2016-09-14 23:38:12 +00:00
|
|
|
old.Claims = identity
|
2016-07-25 20:00:28 +00:00
|
|
|
old.ConnectorID = "connID"
|
|
|
|
return old, nil
|
|
|
|
}); err != nil {
|
|
|
|
t.Fatalf("failed to update auth request: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
got, err := s.GetAuthRequest(a1.ID)
|
2016-07-25 20:00:28 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to get auth req: %v", err)
|
|
|
|
}
|
2016-09-14 23:38:12 +00:00
|
|
|
if !reflect.DeepEqual(got.Claims, identity) {
|
|
|
|
t.Fatalf("update failed, wanted identity=%#v got %#v", identity, got.Claims)
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
2017-03-15 22:00:55 +00:00
|
|
|
|
PKCE implementation (#1784)
* Basic implementation of PKCE
Signed-off-by: Tadeusz Magura-Witkowski <tadeuszmw@gmail.com>
* @mfmarche on 24 Feb: when code_verifier is set, don't check client_secret
In PKCE flow, no client_secret is used, so the check for a valid client_secret
would always fail.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* @deric on 16 Jun: return invalid_grant when wrong code_verifier
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Enforce PKCE flow on /token when PKCE flow was started on /auth
Also dissallow PKCE on /token, when PKCE flow was not started on /auth
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* fixed error messages when mixed PKCE/no PKCE flow.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* server_test.go: Added PKCE error cases on /token endpoint
* Added test for invalid_grant, when wrong code_verifier is sent
* Added test for mixed PKCE / no PKCE auth flows.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* cleanup: extracted method checkErrorResponse and type TestDefinition
* fixed connector being overwritten
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* /token endpoint: skip client_secret verification only for grand type authorization_code with PKCE extension
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Allow "Authorization" header in CORS handlers
* Adds "Authorization" to the default CORS headers{"Accept", "Accept-Language", "Content-Language", "Origin"}
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Add "code_challenge_methods_supported" to discovery endpoint
discovery endpoint /dex/.well-known/openid-configuration
now has the following entry:
"code_challenge_methods_supported": [
"S256",
"plain"
]
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Updated tests (mixed-up comments), added a PKCE test
* @asoorm added test that checks if downgrade to "plain" on /token endpoint
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* remove redefinition of providedCodeVerifier, fixed spelling (#6)
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
Signed-off-by: Bernd Eckstein <HEllRZA@users.noreply.github.com>
* Rename struct CodeChallenge to PKCE
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* PKCE: Check clientSecret when available
In authorization_code flow with PKCE, allow empty client_secret on /auth and /token endpoints. But check the client_secret when it is given.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Enable PKCE with public: true
dex configuration public on staticClients now enables the following behavior in PKCE:
- Public: false, PKCE will always check client_secret. This means PKCE in it's natural form is disabled.
- Public: true, PKCE is enabled. It will only check client_secret if the client has sent one. But it allows the code flow if the client didn't sent one.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Redirect error on unsupported code_challenge_method
- Check for unsupported code_challenge_method after redirect uri is validated, and use newErr() to return the error.
- Add PKCE tests to oauth2_test.go
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Reverted go.mod and go.sum to the state of master
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Don't omit client secret check for PKCE
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Allow public clients (e.g. with PKCE) to have redirect URIs configured
Signed-off-by: Martin Heide <martin.heide@faro.com>
* Remove "Authorization" as Accepted Headers on CORS, small fixes
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Revert "Allow public clients (e.g. with PKCE) to have redirect URIs configured"
This reverts commit b6e297b78537dc44cd3e1374f0b4d34bf89404ac.
Signed-off-by: Martin Heide <martin.heide@faro.com>
* PKCE on client_secret client error message
* When connecting to the token endpoint with PKCE without client_secret, but the client is configured with a client_secret, generate a special error message.
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* Output info message when PKCE without client_secret used on confidential client
* removes the special error message
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
* General missing/invalid client_secret message on token endpoint
Signed-off-by: Bernd Eckstein <Bernd.Eckstein@faro.com>
Co-authored-by: Tadeusz Magura-Witkowski <tadeuszmw@gmail.com>
Co-authored-by: Martin Heide <martin.heide@faro.com>
Co-authored-by: M. Heide <66078329+heidemn-faro@users.noreply.github.com>
2020-10-26 10:33:40 +00:00
|
|
|
if !reflect.DeepEqual(got.PKCE, codeChallenge) {
|
|
|
|
t.Fatalf("storage does not support PKCE, wanted challenge=%#v got %#v", codeChallenge, got.PKCE)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.DeleteAuthRequest(a1.ID); err != nil {
|
|
|
|
t.Fatalf("failed to delete auth request: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.DeleteAuthRequest(a2.ID); err != nil {
|
|
|
|
t.Fatalf("failed to delete auth request: %v", err)
|
|
|
|
}
|
|
|
|
|
2019-09-24 09:20:35 +00:00
|
|
|
_, err = s.GetAuthRequest(a1.ID)
|
|
|
|
mustBeErrNotFound(t, "auth request", err)
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 15:41:54 +00:00
|
|
|
func testAuthCodeCRUD(t *testing.T, s storage.Storage) {
|
2017-03-15 22:00:55 +00:00
|
|
|
a1 := storage.AuthCode{
|
2016-09-19 15:41:54 +00:00
|
|
|
ID: storage.NewID(),
|
2017-03-15 22:00:55 +00:00
|
|
|
ClientID: "client1",
|
2016-09-19 15:41:54 +00:00
|
|
|
RedirectURI: "https://localhost:80/callback",
|
|
|
|
Nonce: "foobar",
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
Expiry: neverExpire,
|
|
|
|
ConnectorID: "ldap",
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
2021-01-05 03:49:12 +00:00
|
|
|
PKCE: storage.PKCE{
|
|
|
|
CodeChallenge: "12345",
|
|
|
|
CodeChallengeMethod: "Whatever",
|
|
|
|
},
|
2016-09-19 15:41:54 +00:00
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "1",
|
|
|
|
Username: "jane",
|
|
|
|
Email: "jane.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a", "b"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.CreateAuthCode(a1); err != nil {
|
2016-09-19 15:41:54 +00:00
|
|
|
t.Fatalf("failed creating auth code: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
a2 := storage.AuthCode{
|
|
|
|
ID: storage.NewID(),
|
|
|
|
ClientID: "client2",
|
|
|
|
RedirectURI: "https://localhost:80/callback",
|
|
|
|
Nonce: "foobar",
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
Expiry: neverExpire,
|
|
|
|
ConnectorID: "ldap",
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "2",
|
|
|
|
Username: "john",
|
|
|
|
Email: "john.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2017-02-21 23:00:22 +00:00
|
|
|
// Attempt to create same AuthCode twice.
|
2017-03-15 22:00:55 +00:00
|
|
|
err := s.CreateAuthCode(a1)
|
2017-02-21 23:00:22 +00:00
|
|
|
mustBeErrAlreadyExists(t, "auth code", err)
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.CreateAuthCode(a2); err != nil {
|
|
|
|
t.Fatalf("failed creating auth code: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
got, err := s.GetAuthCode(a1.ID)
|
2016-09-19 15:41:54 +00:00
|
|
|
if err != nil {
|
2019-09-24 09:20:35 +00:00
|
|
|
t.Fatalf("failed to get auth code: %v", err)
|
2016-09-19 15:41:54 +00:00
|
|
|
}
|
2017-03-15 22:00:55 +00:00
|
|
|
if a1.Expiry.Unix() != got.Expiry.Unix() {
|
|
|
|
t.Errorf("auth code expiry did not match want=%s vs got=%s", a1.Expiry, got.Expiry)
|
2016-09-19 15:41:54 +00:00
|
|
|
}
|
2017-03-15 22:00:55 +00:00
|
|
|
got.Expiry = a1.Expiry // time fields do not compare well
|
|
|
|
if diff := pretty.Compare(a1, got); diff != "" {
|
2016-09-19 15:41:54 +00:00
|
|
|
t.Errorf("auth code retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.DeleteAuthCode(a1.ID); err != nil {
|
|
|
|
t.Fatalf("delete auth code: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.DeleteAuthCode(a2.ID); err != nil {
|
2016-09-19 15:41:54 +00:00
|
|
|
t.Fatalf("delete auth code: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
_, err = s.GetAuthCode(a1.ID)
|
2016-09-19 15:41:54 +00:00
|
|
|
mustBeErrNotFound(t, "auth code", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testClientCRUD(t *testing.T, s storage.Storage) {
|
2017-03-15 22:00:55 +00:00
|
|
|
id1 := storage.NewID()
|
|
|
|
c1 := storage.Client{
|
|
|
|
ID: id1,
|
2016-09-19 15:41:54 +00:00
|
|
|
Secret: "foobar",
|
|
|
|
RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"},
|
|
|
|
Name: "dex client",
|
|
|
|
LogoURL: "https://goo.gl/JIyzIC",
|
|
|
|
}
|
2017-03-15 22:00:55 +00:00
|
|
|
err := s.DeleteClient(id1)
|
2016-09-19 15:41:54 +00:00
|
|
|
mustBeErrNotFound(t, "client", err)
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.CreateClient(c1); err != nil {
|
2016-09-19 15:41:54 +00:00
|
|
|
t.Fatalf("create client: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-02-21 23:00:22 +00:00
|
|
|
// Attempt to create same Client twice.
|
2017-03-15 22:00:55 +00:00
|
|
|
err = s.CreateClient(c1)
|
2017-02-21 23:00:22 +00:00
|
|
|
mustBeErrAlreadyExists(t, "client", err)
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
id2 := storage.NewID()
|
|
|
|
c2 := storage.Client{
|
|
|
|
ID: id2,
|
|
|
|
Secret: "barfoo",
|
|
|
|
RedirectURIs: []string{"foo://bar.com/", "https://auth.example.com"},
|
|
|
|
Name: "dex client",
|
|
|
|
LogoURL: "https://goo.gl/JIyzIC",
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateClient(c2); err != nil {
|
|
|
|
t.Fatalf("create client: %v", err)
|
|
|
|
}
|
|
|
|
|
2021-01-15 15:22:38 +00:00
|
|
|
getAndCompare := func(_ string, want storage.Client) {
|
2017-03-15 22:00:55 +00:00
|
|
|
gc, err := s.GetClient(id1)
|
2016-09-19 15:41:54 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("get client: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if diff := pretty.Compare(want, gc); diff != "" {
|
|
|
|
t.Errorf("client retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
getAndCompare(id1, c1)
|
2016-09-19 15:41:54 +00:00
|
|
|
|
|
|
|
newSecret := "barfoo"
|
2017-03-15 22:00:55 +00:00
|
|
|
err = s.UpdateClient(id1, func(old storage.Client) (storage.Client, error) {
|
2016-09-19 15:41:54 +00:00
|
|
|
old.Secret = newSecret
|
|
|
|
return old, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("update client: %v", err)
|
|
|
|
}
|
2017-03-15 22:00:55 +00:00
|
|
|
c1.Secret = newSecret
|
|
|
|
getAndCompare(id1, c1)
|
|
|
|
|
|
|
|
if err := s.DeleteClient(id1); err != nil {
|
|
|
|
t.Fatalf("delete client: %v", err)
|
|
|
|
}
|
2016-09-19 15:41:54 +00:00
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.DeleteClient(id2); err != nil {
|
2016-09-19 15:41:54 +00:00
|
|
|
t.Fatalf("delete client: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
_, err = s.GetClient(id1)
|
2016-09-19 15:41:54 +00:00
|
|
|
mustBeErrNotFound(t, "client", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testRefreshTokenCRUD(t *testing.T, s storage.Storage) {
|
2016-08-03 04:57:36 +00:00
|
|
|
id := storage.NewID()
|
|
|
|
refresh := storage.RefreshToken{
|
2016-12-22 23:56:09 +00:00
|
|
|
ID: id,
|
|
|
|
Token: "bar",
|
|
|
|
Nonce: "foo",
|
|
|
|
ClientID: "client_id",
|
|
|
|
ConnectorID: "client_secret",
|
|
|
|
Scopes: []string{"openid", "email", "profile"},
|
|
|
|
CreatedAt: time.Now().UTC().Round(time.Millisecond),
|
|
|
|
LastUsed: time.Now().UTC().Round(time.Millisecond),
|
2016-09-19 15:41:54 +00:00
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "1",
|
|
|
|
Username: "jane",
|
|
|
|
Email: "jane.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a", "b"},
|
|
|
|
},
|
2017-01-09 23:01:29 +00:00
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
|
|
|
if err := s.CreateRefresh(refresh); err != nil {
|
|
|
|
t.Fatalf("create refresh token: %v", err)
|
|
|
|
}
|
2016-09-19 15:41:54 +00:00
|
|
|
|
2017-02-21 23:00:22 +00:00
|
|
|
// Attempt to create same Refresh Token twice.
|
|
|
|
err := s.CreateRefresh(refresh)
|
|
|
|
mustBeErrAlreadyExists(t, "refresh token", err)
|
|
|
|
|
2016-09-19 15:41:54 +00:00
|
|
|
getAndCompare := func(id string, want storage.RefreshToken) {
|
|
|
|
gr, err := s.GetRefresh(id)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("get refresh: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2018-11-15 16:27:20 +00:00
|
|
|
|
|
|
|
if diff := pretty.Compare(gr.CreatedAt.UnixNano(), gr.CreatedAt.UnixNano()); diff != "" {
|
|
|
|
t.Errorf("refresh token created timestamp retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
|
|
|
|
|
|
|
if diff := pretty.Compare(gr.LastUsed.UnixNano(), gr.LastUsed.UnixNano()); diff != "" {
|
|
|
|
t.Errorf("refresh token last used timestamp retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
|
|
|
|
|
|
|
gr.CreatedAt = time.Time{}
|
|
|
|
gr.LastUsed = time.Time{}
|
|
|
|
want.CreatedAt = time.Time{}
|
|
|
|
want.LastUsed = time.Time{}
|
|
|
|
|
2016-09-19 15:41:54 +00:00
|
|
|
if diff := pretty.Compare(want, gr); diff != "" {
|
|
|
|
t.Errorf("refresh token retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 15:41:54 +00:00
|
|
|
getAndCompare(id, refresh)
|
|
|
|
|
2017-03-13 22:53:28 +00:00
|
|
|
id2 := storage.NewID()
|
|
|
|
refresh2 := storage.RefreshToken{
|
|
|
|
ID: id2,
|
|
|
|
Token: "bar_2",
|
|
|
|
Nonce: "foo_2",
|
|
|
|
ClientID: "client_id_2",
|
|
|
|
ConnectorID: "client_secret",
|
|
|
|
Scopes: []string{"openid", "email", "profile"},
|
|
|
|
CreatedAt: time.Now().UTC().Round(time.Millisecond),
|
|
|
|
LastUsed: time.Now().UTC().Round(time.Millisecond),
|
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "2",
|
|
|
|
Username: "john",
|
|
|
|
Email: "john.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a", "b"},
|
|
|
|
},
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateRefresh(refresh2); err != nil {
|
|
|
|
t.Fatalf("create second refresh token: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
getAndCompare(id2, refresh2)
|
|
|
|
|
2016-12-22 23:56:09 +00:00
|
|
|
updatedAt := time.Now().UTC().Round(time.Millisecond)
|
|
|
|
|
|
|
|
updater := func(r storage.RefreshToken) (storage.RefreshToken, error) {
|
|
|
|
r.Token = "spam"
|
|
|
|
r.LastUsed = updatedAt
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
if err := s.UpdateRefreshToken(id, updater); err != nil {
|
2020-12-20 03:02:02 +00:00
|
|
|
t.Errorf("failed to update refresh token: %v", err)
|
2016-12-22 23:56:09 +00:00
|
|
|
}
|
|
|
|
refresh.Token = "spam"
|
|
|
|
refresh.LastUsed = updatedAt
|
|
|
|
getAndCompare(id, refresh)
|
|
|
|
|
2017-03-13 22:53:28 +00:00
|
|
|
// Ensure that updating the first token doesn't impact the second. Issue #847.
|
|
|
|
getAndCompare(id2, refresh2)
|
|
|
|
|
2016-07-25 20:00:28 +00:00
|
|
|
if err := s.DeleteRefresh(id); err != nil {
|
|
|
|
t.Fatalf("failed to delete refresh request: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.DeleteRefresh(id2); err != nil {
|
|
|
|
t.Fatalf("failed to delete refresh request: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-02-21 23:00:22 +00:00
|
|
|
_, err = s.GetRefresh(id)
|
|
|
|
mustBeErrNotFound(t, "refresh token", err)
|
2016-10-05 23:03:53 +00:00
|
|
|
}
|
2016-07-25 20:00:28 +00:00
|
|
|
|
2016-11-17 22:50:58 +00:00
|
|
|
type byEmail []storage.Password
|
|
|
|
|
|
|
|
func (n byEmail) Len() int { return len(n) }
|
|
|
|
func (n byEmail) Less(i, j int) bool { return n[i].Email < n[j].Email }
|
|
|
|
func (n byEmail) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
|
|
|
|
|
2016-10-05 23:03:53 +00:00
|
|
|
func testPasswordCRUD(t *testing.T, s storage.Storage) {
|
|
|
|
// Use bcrypt.MinCost to keep the tests short.
|
2017-03-15 22:00:55 +00:00
|
|
|
passwordHash1, err := bcrypt.GenerateFromPassword([]byte("secret"), bcrypt.MinCost)
|
2016-10-05 23:03:53 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
password1 := storage.Password{
|
2016-10-05 23:03:53 +00:00
|
|
|
Email: "jane@example.com",
|
2017-03-15 22:00:55 +00:00
|
|
|
Hash: passwordHash1,
|
2016-10-05 23:03:53 +00:00
|
|
|
Username: "jane",
|
|
|
|
UserID: "foobar",
|
|
|
|
}
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.CreatePassword(password1); err != nil {
|
2016-10-05 23:03:53 +00:00
|
|
|
t.Fatalf("create password token: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-02-21 23:00:22 +00:00
|
|
|
// Attempt to create same Password twice.
|
2017-03-15 22:00:55 +00:00
|
|
|
err = s.CreatePassword(password1)
|
2017-02-21 23:00:22 +00:00
|
|
|
mustBeErrAlreadyExists(t, "password", err)
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
passwordHash2, err := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.MinCost)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
password2 := storage.Password{
|
|
|
|
Email: "john@example.com",
|
|
|
|
Hash: passwordHash2,
|
|
|
|
Username: "john",
|
|
|
|
UserID: "barfoo",
|
|
|
|
}
|
|
|
|
if err := s.CreatePassword(password2); err != nil {
|
|
|
|
t.Fatalf("create password token: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-10-05 23:03:53 +00:00
|
|
|
getAndCompare := func(id string, want storage.Password) {
|
|
|
|
gr, err := s.GetPassword(id)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("get password %q: %v", id, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if diff := pretty.Compare(want, gr); diff != "" {
|
|
|
|
t.Errorf("password retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
getAndCompare("jane@example.com", password1)
|
|
|
|
getAndCompare("JANE@example.com", password1) // Emails should be case insensitive
|
2016-10-05 23:03:53 +00:00
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.UpdatePassword(password1.Email, func(old storage.Password) (storage.Password, error) {
|
2016-10-05 23:03:53 +00:00
|
|
|
old.Username = "jane doe"
|
|
|
|
return old, nil
|
|
|
|
}); err != nil {
|
|
|
|
t.Fatalf("failed to update auth request: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
password1.Username = "jane doe"
|
|
|
|
getAndCompare("jane@example.com", password1)
|
2016-10-05 23:03:53 +00:00
|
|
|
|
2016-11-16 22:57:27 +00:00
|
|
|
var passwordList []storage.Password
|
2017-03-15 22:00:55 +00:00
|
|
|
passwordList = append(passwordList, password1, password2)
|
2016-11-16 22:57:27 +00:00
|
|
|
|
|
|
|
listAndCompare := func(want []storage.Password) {
|
|
|
|
passwords, err := s.ListPasswords()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("list password: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2016-11-17 22:50:58 +00:00
|
|
|
sort.Sort(byEmail(want))
|
|
|
|
sort.Sort(byEmail(passwords))
|
2016-11-16 22:57:27 +00:00
|
|
|
if diff := pretty.Compare(want, passwords); diff != "" {
|
|
|
|
t.Errorf("password list retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
listAndCompare(passwordList)
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.DeletePassword(password1.Email); err != nil {
|
|
|
|
t.Fatalf("failed to delete password: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.DeletePassword(password2.Email); err != nil {
|
2016-10-05 23:03:53 +00:00
|
|
|
t.Fatalf("failed to delete password: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
_, err = s.GetPassword(password1.Email)
|
2017-02-21 23:00:22 +00:00
|
|
|
mustBeErrNotFound(t, "password", err)
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
2016-10-13 01:47:15 +00:00
|
|
|
|
2017-02-01 00:11:59 +00:00
|
|
|
func testOfflineSessionCRUD(t *testing.T, s storage.Storage) {
|
2017-03-15 22:00:55 +00:00
|
|
|
userID1 := storage.NewID()
|
|
|
|
session1 := storage.OfflineSessions{
|
2018-01-30 11:18:00 +00:00
|
|
|
UserID: userID1,
|
|
|
|
ConnID: "Conn1",
|
|
|
|
Refresh: make(map[string]*storage.RefreshTokenRef),
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
2017-02-01 00:11:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Creating an OfflineSession with an empty Refresh list to ensure that
|
|
|
|
// an empty map is translated as expected by the storage.
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.CreateOfflineSessions(session1); err != nil {
|
|
|
|
t.Fatalf("create offline session with UserID = %s: %v", session1.UserID, err)
|
2017-02-01 00:11:59 +00:00
|
|
|
}
|
|
|
|
|
2017-02-21 23:00:22 +00:00
|
|
|
// Attempt to create same OfflineSession twice.
|
2017-03-15 22:00:55 +00:00
|
|
|
err := s.CreateOfflineSessions(session1)
|
2017-02-21 23:00:22 +00:00
|
|
|
mustBeErrAlreadyExists(t, "offline session", err)
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
userID2 := storage.NewID()
|
|
|
|
session2 := storage.OfflineSessions{
|
2018-01-30 11:18:00 +00:00
|
|
|
UserID: userID2,
|
|
|
|
ConnID: "Conn2",
|
|
|
|
Refresh: make(map[string]*storage.RefreshTokenRef),
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
2017-03-15 22:00:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateOfflineSessions(session2); err != nil {
|
|
|
|
t.Fatalf("create offline session with UserID = %s: %v", session2.UserID, err)
|
|
|
|
}
|
|
|
|
|
2017-02-01 00:11:59 +00:00
|
|
|
getAndCompare := func(userID string, connID string, want storage.OfflineSessions) {
|
|
|
|
gr, err := s.GetOfflineSessions(userID, connID)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("get offline session: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if diff := pretty.Compare(want, gr); diff != "" {
|
|
|
|
t.Errorf("offline session retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
getAndCompare(userID1, "Conn1", session1)
|
2017-02-01 00:11:59 +00:00
|
|
|
|
|
|
|
id := storage.NewID()
|
|
|
|
tokenRef := storage.RefreshTokenRef{
|
|
|
|
ID: id,
|
|
|
|
ClientID: "client_id",
|
|
|
|
CreatedAt: time.Now().UTC().Round(time.Millisecond),
|
|
|
|
LastUsed: time.Now().UTC().Round(time.Millisecond),
|
|
|
|
}
|
2017-03-15 22:00:55 +00:00
|
|
|
session1.Refresh[tokenRef.ClientID] = &tokenRef
|
2017-02-01 00:11:59 +00:00
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.UpdateOfflineSessions(session1.UserID, session1.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {
|
2017-02-01 00:11:59 +00:00
|
|
|
old.Refresh[tokenRef.ClientID] = &tokenRef
|
|
|
|
return old, nil
|
|
|
|
}); err != nil {
|
|
|
|
t.Fatalf("failed to update offline session: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
getAndCompare(userID1, "Conn1", session1)
|
2017-02-01 00:11:59 +00:00
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.DeleteOfflineSessions(session1.UserID, session1.ConnID); err != nil {
|
2017-02-01 00:11:59 +00:00
|
|
|
t.Fatalf("failed to delete offline session: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
if err := s.DeleteOfflineSessions(session2.UserID, session2.ConnID); err != nil {
|
|
|
|
t.Fatalf("failed to delete offline session: %v", err)
|
|
|
|
}
|
2017-02-01 00:11:59 +00:00
|
|
|
|
2017-03-15 22:00:55 +00:00
|
|
|
_, err = s.GetOfflineSessions(session1.UserID, session1.ConnID)
|
|
|
|
mustBeErrNotFound(t, "offline session", err)
|
2017-02-01 00:11:59 +00:00
|
|
|
}
|
|
|
|
|
2017-03-23 16:59:33 +00:00
|
|
|
func testConnectorCRUD(t *testing.T, s storage.Storage) {
|
|
|
|
id1 := storage.NewID()
|
|
|
|
config1 := []byte(`{"issuer": "https://accounts.google.com"}`)
|
|
|
|
c1 := storage.Connector{
|
2020-04-07 09:02:44 +00:00
|
|
|
ID: id1,
|
|
|
|
Type: "Default",
|
|
|
|
Name: "Default",
|
|
|
|
Config: config1,
|
2017-03-23 16:59:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateConnector(c1); err != nil {
|
|
|
|
t.Fatalf("create connector with ID = %s: %v", c1.ID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to create same Connector twice.
|
|
|
|
err := s.CreateConnector(c1)
|
|
|
|
mustBeErrAlreadyExists(t, "connector", err)
|
|
|
|
|
|
|
|
id2 := storage.NewID()
|
2020-12-20 03:02:03 +00:00
|
|
|
config2 := []byte(`{"redirectURI": "http://127.0.0.1:5556/dex/callback"}`)
|
2017-03-23 16:59:33 +00:00
|
|
|
c2 := storage.Connector{
|
2020-04-07 09:02:44 +00:00
|
|
|
ID: id2,
|
|
|
|
Type: "Mock",
|
|
|
|
Name: "Mock",
|
|
|
|
Config: config2,
|
2017-03-23 16:59:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateConnector(c2); err != nil {
|
|
|
|
t.Fatalf("create connector with ID = %s: %v", c2.ID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
getAndCompare := func(id string, want storage.Connector) {
|
|
|
|
gr, err := s.GetConnector(id)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("get connector: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2020-04-07 09:02:44 +00:00
|
|
|
// ignore resource version comparison
|
|
|
|
gr.ResourceVersion = ""
|
2017-03-23 16:59:33 +00:00
|
|
|
if diff := pretty.Compare(want, gr); diff != "" {
|
|
|
|
t.Errorf("connector retrieved from storage did not match: %s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getAndCompare(id1, c1)
|
|
|
|
|
|
|
|
if err := s.UpdateConnector(c1.ID, func(old storage.Connector) (storage.Connector, error) {
|
|
|
|
old.Type = "oidc"
|
|
|
|
return old, nil
|
|
|
|
}); err != nil {
|
|
|
|
t.Fatalf("failed to update Connector: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c1.Type = "oidc"
|
|
|
|
getAndCompare(id1, c1)
|
|
|
|
|
2017-10-27 11:09:33 +00:00
|
|
|
connectorList := []storage.Connector{c1, c2}
|
|
|
|
listAndCompare := func(want []storage.Connector) {
|
|
|
|
connectors, err := s.ListConnectors()
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("list connectors: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2020-04-07 09:02:44 +00:00
|
|
|
// ignore resource version comparison
|
|
|
|
for i := range connectors {
|
|
|
|
connectors[i].ResourceVersion = ""
|
|
|
|
}
|
2017-10-27 11:09:33 +00:00
|
|
|
sort.Slice(connectors, func(i, j int) bool {
|
|
|
|
return connectors[i].Name < connectors[j].Name
|
|
|
|
})
|
|
|
|
if diff := pretty.Compare(want, connectors); diff != "" {
|
2020-04-07 09:02:44 +00:00
|
|
|
t.Errorf("connector list retrieved from storage did not match: %s", diff)
|
2017-10-27 11:09:33 +00:00
|
|
|
}
|
2017-04-17 22:41:41 +00:00
|
|
|
}
|
2017-10-27 11:09:33 +00:00
|
|
|
listAndCompare(connectorList)
|
2017-04-17 22:41:41 +00:00
|
|
|
|
2017-03-23 16:59:33 +00:00
|
|
|
if err := s.DeleteConnector(c1.ID); err != nil {
|
|
|
|
t.Fatalf("failed to delete connector: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.DeleteConnector(c2.ID); err != nil {
|
|
|
|
t.Fatalf("failed to delete connector: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.GetConnector(c1.ID)
|
|
|
|
mustBeErrNotFound(t, "connector", err)
|
|
|
|
}
|
|
|
|
|
2016-10-14 18:56:31 +00:00
|
|
|
func testKeysCRUD(t *testing.T, s storage.Storage) {
|
|
|
|
updateAndCompare := func(k storage.Keys) {
|
|
|
|
err := s.UpdateKeys(func(oldKeys storage.Keys) (storage.Keys, error) {
|
|
|
|
return k, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("failed to update keys: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if got, err := s.GetKeys(); err != nil {
|
|
|
|
t.Errorf("failed to get keys: %v", err)
|
|
|
|
} else {
|
|
|
|
got.NextRotation = got.NextRotation.UTC()
|
|
|
|
if diff := pretty.Compare(k, got); diff != "" {
|
|
|
|
t.Errorf("got keys did not equal expected: %s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Postgres isn't as accurate with nano seconds as we'd like
|
|
|
|
n := time.Now().UTC().Round(time.Second)
|
|
|
|
|
|
|
|
keys1 := storage.Keys{
|
|
|
|
SigningKey: jsonWebKeys[0].Private,
|
|
|
|
SigningKeyPub: jsonWebKeys[0].Public,
|
|
|
|
NextRotation: n,
|
|
|
|
}
|
|
|
|
|
|
|
|
keys2 := storage.Keys{
|
|
|
|
SigningKey: jsonWebKeys[2].Private,
|
|
|
|
SigningKeyPub: jsonWebKeys[2].Public,
|
|
|
|
NextRotation: n.Add(time.Hour),
|
|
|
|
VerificationKeys: []storage.VerificationKey{
|
|
|
|
{
|
|
|
|
PublicKey: jsonWebKeys[0].Public,
|
|
|
|
Expiry: n.Add(time.Hour),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
PublicKey: jsonWebKeys[1].Public,
|
|
|
|
Expiry: n.Add(time.Hour * 2),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
updateAndCompare(keys1)
|
|
|
|
updateAndCompare(keys2)
|
|
|
|
}
|
|
|
|
|
2016-10-13 01:47:15 +00:00
|
|
|
func testGC(t *testing.T, s storage.Storage) {
|
2016-12-16 19:03:36 +00:00
|
|
|
est, err := time.LoadLocation("America/New_York")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
pst, err := time.LoadLocation("America/Los_Angeles")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
expiry := time.Now().In(est)
|
2016-10-13 01:47:15 +00:00
|
|
|
c := storage.AuthCode{
|
|
|
|
ID: storage.NewID(),
|
|
|
|
ClientID: "foobar",
|
|
|
|
RedirectURI: "https://localhost:80/callback",
|
|
|
|
Nonce: "foobar",
|
|
|
|
Scopes: []string{"openid", "email"},
|
2016-12-16 19:03:36 +00:00
|
|
|
Expiry: expiry,
|
2016-10-13 01:47:15 +00:00
|
|
|
ConnectorID: "ldap",
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "1",
|
|
|
|
Username: "jane",
|
|
|
|
Email: "jane.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a", "b"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateAuthCode(c); err != nil {
|
|
|
|
t.Fatalf("failed creating auth code: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-12-16 19:03:36 +00:00
|
|
|
for _, tz := range []*time.Location{time.UTC, est, pst} {
|
|
|
|
result, err := s.GarbageCollect(expiry.Add(-time.Hour).In(tz))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("garbage collection failed: %v", err)
|
2020-10-17 21:54:27 +00:00
|
|
|
} else if result.AuthCodes != 0 || result.AuthRequests != 0 {
|
|
|
|
t.Errorf("expected no garbage collection results, got %#v", result)
|
2016-12-16 19:03:36 +00:00
|
|
|
}
|
|
|
|
if _, err := s.GetAuthCode(c.ID); err != nil {
|
|
|
|
t.Errorf("expected to be able to get auth code after GC: %v", err)
|
|
|
|
}
|
2016-10-13 01:47:15 +00:00
|
|
|
}
|
|
|
|
|
2016-12-16 19:03:36 +00:00
|
|
|
if r, err := s.GarbageCollect(expiry.Add(time.Hour)); err != nil {
|
2016-10-13 01:47:15 +00:00
|
|
|
t.Errorf("garbage collection failed: %v", err)
|
|
|
|
} else if r.AuthCodes != 1 {
|
|
|
|
t.Errorf("expected to garbage collect 1 objects, got %d", r.AuthCodes)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := s.GetAuthCode(c.ID); err == nil {
|
|
|
|
t.Errorf("expected auth code to be GC'd")
|
|
|
|
} else if err != storage.ErrNotFound {
|
|
|
|
t.Errorf("expected storage.ErrNotFound, got %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
a := storage.AuthRequest{
|
|
|
|
ID: storage.NewID(),
|
|
|
|
ClientID: "foobar",
|
|
|
|
ResponseTypes: []string{"code"},
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
RedirectURI: "https://localhost:80/callback",
|
|
|
|
Nonce: "foo",
|
|
|
|
State: "bar",
|
|
|
|
ForceApprovalPrompt: true,
|
|
|
|
LoggedIn: true,
|
2016-12-16 19:03:36 +00:00
|
|
|
Expiry: expiry,
|
2016-10-13 01:47:15 +00:00
|
|
|
ConnectorID: "ldap",
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "1",
|
|
|
|
Username: "jane",
|
|
|
|
Email: "jane.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a", "b"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateAuthRequest(a); err != nil {
|
|
|
|
t.Fatalf("failed creating auth request: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-12-16 19:03:36 +00:00
|
|
|
for _, tz := range []*time.Location{time.UTC, est, pst} {
|
|
|
|
result, err := s.GarbageCollect(expiry.Add(-time.Hour).In(tz))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("garbage collection failed: %v", err)
|
2020-10-17 21:54:27 +00:00
|
|
|
} else if result.AuthCodes != 0 || result.AuthRequests != 0 {
|
|
|
|
t.Errorf("expected no garbage collection results, got %#v", result)
|
2016-12-16 19:03:36 +00:00
|
|
|
}
|
|
|
|
if _, err := s.GetAuthRequest(a.ID); err != nil {
|
2019-09-24 09:20:35 +00:00
|
|
|
t.Errorf("expected to be able to get auth request after GC: %v", err)
|
2016-12-16 19:03:36 +00:00
|
|
|
}
|
2016-10-13 01:47:15 +00:00
|
|
|
}
|
|
|
|
|
2016-12-16 19:03:36 +00:00
|
|
|
if r, err := s.GarbageCollect(expiry.Add(time.Hour)); err != nil {
|
2016-10-13 01:47:15 +00:00
|
|
|
t.Errorf("garbage collection failed: %v", err)
|
|
|
|
} else if r.AuthRequests != 1 {
|
|
|
|
t.Errorf("expected to garbage collect 1 objects, got %d", r.AuthRequests)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := s.GetAuthRequest(a.ID); err == nil {
|
2019-09-24 09:20:35 +00:00
|
|
|
t.Errorf("expected auth request to be GC'd")
|
2016-10-13 01:47:15 +00:00
|
|
|
} else if err != storage.ErrNotFound {
|
|
|
|
t.Errorf("expected storage.ErrNotFound, got %v", err)
|
|
|
|
}
|
2020-01-16 15:55:07 +00:00
|
|
|
|
|
|
|
d := storage.DeviceRequest{
|
2021-01-15 15:22:38 +00:00
|
|
|
UserCode: storage.NewUserCode(),
|
2020-02-04 15:07:18 +00:00
|
|
|
DeviceCode: storage.NewID(),
|
|
|
|
ClientID: "client1",
|
|
|
|
ClientSecret: "secret1",
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
Expiry: expiry,
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateDeviceRequest(d); err != nil {
|
|
|
|
t.Fatalf("failed creating device request: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tz := range []*time.Location{time.UTC, est, pst} {
|
|
|
|
result, err := s.GarbageCollect(expiry.Add(-time.Hour).In(tz))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("garbage collection failed: %v", err)
|
2020-10-17 21:54:27 +00:00
|
|
|
} else if result.DeviceRequests != 0 {
|
|
|
|
t.Errorf("expected no device garbage collection results, got %#v", result)
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
2020-02-04 15:07:18 +00:00
|
|
|
if _, err := s.GetDeviceRequest(d.UserCode); err != nil {
|
|
|
|
t.Errorf("expected to be able to get auth request after GC: %v", err)
|
|
|
|
}
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
|
|
|
if r, err := s.GarbageCollect(expiry.Add(time.Hour)); err != nil {
|
|
|
|
t.Errorf("garbage collection failed: %v", err)
|
|
|
|
} else if r.DeviceRequests != 1 {
|
|
|
|
t.Errorf("expected to garbage collect 1 device request, got %d", r.DeviceRequests)
|
|
|
|
}
|
|
|
|
|
2020-02-04 15:07:18 +00:00
|
|
|
if _, err := s.GetDeviceRequest(d.UserCode); err == nil {
|
|
|
|
t.Errorf("expected device request to be GC'd")
|
|
|
|
} else if err != storage.ErrNotFound {
|
|
|
|
t.Errorf("expected storage.ErrNotFound, got %v", err)
|
|
|
|
}
|
2020-01-16 15:55:07 +00:00
|
|
|
|
|
|
|
dt := storage.DeviceToken{
|
2020-02-04 15:07:18 +00:00
|
|
|
DeviceCode: storage.NewID(),
|
|
|
|
Status: "pending",
|
|
|
|
Token: "foo",
|
|
|
|
Expiry: expiry,
|
|
|
|
LastRequestTime: time.Now(),
|
|
|
|
PollIntervalSeconds: 0,
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateDeviceToken(dt); err != nil {
|
|
|
|
t.Fatalf("failed creating device token: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tz := range []*time.Location{time.UTC, est, pst} {
|
|
|
|
result, err := s.GarbageCollect(expiry.Add(-time.Hour).In(tz))
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("garbage collection failed: %v", err)
|
2020-10-17 21:54:27 +00:00
|
|
|
} else if result.DeviceTokens != 0 {
|
|
|
|
t.Errorf("expected no device token garbage collection results, got %#v", result)
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
2020-01-27 15:35:37 +00:00
|
|
|
if _, err := s.GetDeviceToken(dt.DeviceCode); err != nil {
|
|
|
|
t.Errorf("expected to be able to get device token after GC: %v", err)
|
|
|
|
}
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
|
|
|
if r, err := s.GarbageCollect(expiry.Add(time.Hour)); err != nil {
|
|
|
|
t.Errorf("garbage collection failed: %v", err)
|
|
|
|
} else if r.DeviceTokens != 1 {
|
|
|
|
t.Errorf("expected to garbage collect 1 device token, got %d", r.DeviceTokens)
|
|
|
|
}
|
|
|
|
|
2020-01-27 15:35:37 +00:00
|
|
|
if _, err := s.GetDeviceToken(dt.DeviceCode); err == nil {
|
|
|
|
t.Errorf("expected device token to be GC'd")
|
|
|
|
} else if err != storage.ErrNotFound {
|
|
|
|
t.Errorf("expected storage.ErrNotFound, got %v", err)
|
|
|
|
}
|
2016-10-13 01:47:15 +00:00
|
|
|
}
|
2016-12-16 19:03:36 +00:00
|
|
|
|
|
|
|
// testTimezones tests that backends either fully support timezones or
|
|
|
|
// do the correct standardization.
|
|
|
|
func testTimezones(t *testing.T, s storage.Storage) {
|
|
|
|
est, err := time.LoadLocation("America/New_York")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
// Create an expiry with timezone info. Only expect backends to be
|
|
|
|
// accurate to the millisecond
|
|
|
|
expiry := time.Now().In(est).Round(time.Millisecond)
|
|
|
|
|
|
|
|
c := storage.AuthCode{
|
|
|
|
ID: storage.NewID(),
|
|
|
|
ClientID: "foobar",
|
|
|
|
RedirectURI: "https://localhost:80/callback",
|
|
|
|
Nonce: "foobar",
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
Expiry: expiry,
|
|
|
|
ConnectorID: "ldap",
|
|
|
|
ConnectorData: []byte(`{"some":"data"}`),
|
|
|
|
Claims: storage.Claims{
|
|
|
|
UserID: "1",
|
|
|
|
Username: "jane",
|
|
|
|
Email: "jane.doe@example.com",
|
|
|
|
EmailVerified: true,
|
|
|
|
Groups: []string{"a", "b"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if err := s.CreateAuthCode(c); err != nil {
|
|
|
|
t.Fatalf("failed creating auth code: %v", err)
|
|
|
|
}
|
|
|
|
got, err := s.GetAuthCode(c.ID)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to get auth code: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that if the resulting time is converted to the same
|
|
|
|
// timezone, it's the same value. We DO NOT expect timezones
|
|
|
|
// to be preserved.
|
|
|
|
gotTime := got.Expiry.In(est)
|
|
|
|
wantTime := expiry
|
|
|
|
if !gotTime.Equal(wantTime) {
|
|
|
|
t.Fatalf("expected expiry %v got %v", wantTime, gotTime)
|
|
|
|
}
|
|
|
|
}
|
2020-01-16 15:55:07 +00:00
|
|
|
|
|
|
|
func testDeviceRequestCRUD(t *testing.T, s storage.Storage) {
|
|
|
|
d1 := storage.DeviceRequest{
|
2021-01-15 15:22:38 +00:00
|
|
|
UserCode: storage.NewUserCode(),
|
2020-02-04 15:07:18 +00:00
|
|
|
DeviceCode: storage.NewID(),
|
|
|
|
ClientID: "client1",
|
|
|
|
ClientSecret: "secret1",
|
|
|
|
Scopes: []string{"openid", "email"},
|
|
|
|
Expiry: neverExpire,
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateDeviceRequest(d1); err != nil {
|
|
|
|
t.Fatalf("failed creating device request: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to create same DeviceRequest twice.
|
2021-01-15 15:22:38 +00:00
|
|
|
err := s.CreateDeviceRequest(d1)
|
2020-01-16 15:55:07 +00:00
|
|
|
mustBeErrAlreadyExists(t, "device request", err)
|
|
|
|
|
2020-10-17 21:54:27 +00:00
|
|
|
// No manual deletes for device requests, will be handled by garbage collection routines
|
|
|
|
// see testGC
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func testDeviceTokenCRUD(t *testing.T, s storage.Storage) {
|
2020-10-17 21:54:27 +00:00
|
|
|
// Create a Token
|
2020-01-16 15:55:07 +00:00
|
|
|
d1 := storage.DeviceToken{
|
2020-01-28 19:14:30 +00:00
|
|
|
DeviceCode: storage.NewID(),
|
|
|
|
Status: "pending",
|
|
|
|
Token: storage.NewID(),
|
|
|
|
Expiry: neverExpire,
|
|
|
|
LastRequestTime: time.Now(),
|
|
|
|
PollIntervalSeconds: 0,
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.CreateDeviceToken(d1); err != nil {
|
|
|
|
t.Fatalf("failed creating device token: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-01-28 19:14:30 +00:00
|
|
|
// Attempt to create same Device Token twice.
|
2020-01-16 15:55:07 +00:00
|
|
|
err := s.CreateDeviceToken(d1)
|
|
|
|
mustBeErrAlreadyExists(t, "device token", err)
|
|
|
|
|
2020-10-17 21:54:27 +00:00
|
|
|
// Update the device token, simulate a redemption
|
2020-01-28 19:14:30 +00:00
|
|
|
if err := s.UpdateDeviceToken(d1.DeviceCode, func(old storage.DeviceToken) (storage.DeviceToken, error) {
|
|
|
|
old.Token = "token data"
|
|
|
|
old.Status = "complete"
|
|
|
|
return old, nil
|
|
|
|
}); err != nil {
|
|
|
|
t.Fatalf("failed to update device token: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-10-17 21:54:27 +00:00
|
|
|
// Retrieve the device token
|
2020-01-28 19:14:30 +00:00
|
|
|
got, err := s.GetDeviceToken(d1.DeviceCode)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to get device token: %v", err)
|
|
|
|
}
|
|
|
|
|
2020-10-17 21:54:27 +00:00
|
|
|
// Validate expected result set
|
2020-01-28 19:14:30 +00:00
|
|
|
if got.Status != "complete" {
|
2020-06-02 18:39:30 +00:00
|
|
|
t.Fatalf("update failed, wanted token status=%v got %v", "complete", got.Status)
|
2020-01-28 19:14:30 +00:00
|
|
|
}
|
|
|
|
if got.Token != "token data" {
|
2020-07-14 14:14:37 +00:00
|
|
|
t.Fatalf("update failed, wanted token %v got %v", "token data", got.Token)
|
2020-01-28 19:14:30 +00:00
|
|
|
}
|
2020-01-16 15:55:07 +00:00
|
|
|
}
|