diff --git a/internal/auth/oauth2.go b/internal/auth/oauth2.go index 81cfbb216..54b040be9 100644 --- a/internal/auth/oauth2.go +++ b/internal/auth/oauth2.go @@ -180,12 +180,19 @@ func (as *AuthenticationService) MintSessionToken(email string, secretKey string return base64.StdEncoding.EncodeToString(freshToken), nil } +// VerifySessionToken calls the exported VerifySessionToken function. +func (as *AuthenticationService) VerifySessionToken(token string, secretKey string) (jwt.Token, error) { + return VerifySessionToken(token, secretKey) +} + // VerifySessionToken symmetrically verifies the validty of the signature on the // access token JWT, returning the parsed token. // // The subject of the token contains the user's email and can be used -// for user object creation. -func (as *AuthenticationService) VerifySessionToken(token string, secretKey string) (jwt.Token, error) { +// for user object creation +// +// This method is exported for use by the mock authenticator. +func VerifySessionToken(token string, secretKey string) (jwt.Token, error) { const op = errors.Op("auth.AuthenticationService.VerifySessionToken") if len(token) == 0 { diff --git a/internal/jimmtest/auth.go b/internal/jimmtest/auth.go index 0d9750ef5..3c99613f1 100644 --- a/internal/jimmtest/auth.go +++ b/internal/jimmtest/auth.go @@ -6,7 +6,10 @@ import ( "context" jujuparams "github.com/juju/juju/rpc/params" + "github.com/lestrrat-go/jwx/v2/jwt" + "github.com/canonical/jimm/internal/auth" + "github.com/canonical/jimm/internal/jimm" "github.com/canonical/jimm/internal/openfga" ) @@ -21,3 +24,18 @@ type Authenticator struct { func (a Authenticator) Authenticate(_ context.Context, _ *jujuparams.LoginRequest) (*openfga.User, error) { return a.User, a.Err } + +type MockOAuthAuthenticator struct { + jimm.OAuthAuthenticator + secretKey string +} + +func NewMockOAuthAuthenticator(secretKey string) MockOAuthAuthenticator { + return MockOAuthAuthenticator{secretKey: secretKey} +} + +// VerifySessionToken provides the mock implementation for verifying session tokens. +// Allowing JIMM tests to create their own session tokens that will always be accepted. +func (m MockOAuthAuthenticator) VerifySessionToken(token string, secretKey string) (jwt.Token, error) { + return auth.VerifySessionToken(token, m.secretKey) +} diff --git a/internal/jimmtest/keycloak.go b/internal/jimmtest/keycloak.go index c52092318..005ce950b 100644 --- a/internal/jimmtest/keycloak.go +++ b/internal/jimmtest/keycloak.go @@ -50,15 +50,18 @@ func CreateRandomKeycloakUser() (*KeycloakUser, error) { } if err := addKeycloakUser(adminCLIToken, email, username); err != nil { + zapctx.Error(context.Background(), "failed to add keycloak user", zap.Error(err)) return nil, errors.E(err, fmt.Sprintf("failed to add keycloak user (%q, %q)", email, username)) } id, err := getKeycloakUserId(adminCLIToken, username) if err != nil { + zapctx.Error(context.Background(), "failed to get keycloak user ID", zap.Error(err)) return nil, errors.E(err, fmt.Sprintf("failed to retrieve ID for newly added keycloak user (%q, %q)", email, username)) } if err := setKeycloakUserPassword(adminCLIToken, id, password); err != nil { + zapctx.Error(context.Background(), "failed to set keycloak user password", zap.Error(err)) return nil, errors.E(err, fmt.Sprintf("failed to set password for newly added keycloak user (%q, %q, %q)", email, username, password)) } return &KeycloakUser{ diff --git a/internal/jimmtest/suite.go b/internal/jimmtest/suite.go index 2ed4b81af..1003f3d4c 100644 --- a/internal/jimmtest/suite.go +++ b/internal/jimmtest/suite.go @@ -10,7 +10,6 @@ import ( "github.com/canonical/candid/candidtest" cofga "github.com/canonical/ofga" - "github.com/coreos/go-oidc/v3/oidc" "github.com/go-chi/chi/v5" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/identchecker" @@ -87,16 +86,8 @@ func (s *JIMMSuite) SetUpTest(c *gc.C) { ctx, cancel := context.WithCancel(context.Background()) s.cancel = cancel - // Connects to a pre-configured keycloak realm - authSvc, err := auth.NewAuthenticationService(ctx, auth.AuthenticationServiceParams{ - IssuerURL: "http://localhost:8082/realms/jimm", - ClientID: "jimm-device", - ClientSecret: "SwjDofnbDzJDm9iyfUhEp67FfUFMY8L4", - Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, - SessionTokenExpiry: time.Hour, - }) - c.Assert(err, gc.Equals, nil) - s.JIMM.OAuthAuthenticator = authSvc + // Note that the secret key here must match what is used in tests. + s.JIMM.OAuthAuthenticator = NewMockOAuthAuthenticator("test-key") err = s.JIMM.Database.Migrate(ctx, false) c.Assert(err, gc.Equals, nil) diff --git a/internal/jujuapi/admin_test.go b/internal/jujuapi/admin_test.go index 59a3d5ed6..c2e353a07 100644 --- a/internal/jujuapi/admin_test.go +++ b/internal/jujuapi/admin_test.go @@ -12,10 +12,13 @@ import ( "net/url" "regexp" "strings" + "time" "github.com/canonical/jimm/api/params" + "github.com/canonical/jimm/internal/auth" "github.com/canonical/jimm/internal/dbmodel" "github.com/canonical/jimm/internal/jimmtest" + "github.com/coreos/go-oidc/v3/oidc" "github.com/juju/juju/api" jujuparams "github.com/juju/juju/rpc/params" gc "gopkg.in/check.v1" @@ -26,6 +29,22 @@ type adminSuite struct { websocketSuite } +func (s *adminSuite) SetUpTest(c *gc.C) { + s.websocketSuite.SetUpTest(c) + ctx := context.Background() + // Replace JIMM's mock authenticator with a real one here + // for testing the login flows. + authSvc, err := auth.NewAuthenticationService(ctx, auth.AuthenticationServiceParams{ + IssuerURL: "http://localhost:8082/realms/jimm", + ClientID: "jimm-device", + ClientSecret: "SwjDofnbDzJDm9iyfUhEp67FfUFMY8L4", + Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, + SessionTokenExpiry: time.Hour, + }) + c.Assert(err, gc.Equals, nil) + s.JIMM.OAuthAuthenticator = authSvc +} + var _ = gc.Suite(&adminSuite{}) func (s *adminSuite) TestLoginToController(c *gc.C) {