func (s *grpcServer) Token(userID, clientID string, iat, exp time.Time) (*jose.JWT, string, error) { signer, err := s.server.KeyManager.Signer() if err != nil { log.Errorf("grpc.go: Failed to generate ID token: %v", err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } user, err := s.server.UserRepo.Get(nil, userID) if err != nil { log.Errorf("grpc.go: Failed to fetch user %q from repo: %v: ", userID, err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } claims := oidc.NewClaims(s.server.IssuerURL.String(), userID, clientID, iat, exp) user.AddToClaims(claims) if user.Admin { claims.Add(OtsimoUserTypeClaim, "adm") } jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { log.Errorf("grpc.go: Failed to generate ID token: %v", err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } refreshToken, err := s.server.RefreshTokenRepo.Create(user.ID, clientID) if err != nil { log.Errorf("grpc.go: Failed to generate refresh token: %v", err) return nil, "", oauth2.NewError(oauth2.ErrorServerError) } return jwt, refreshToken, nil }
func (s *Server) ClientCredsToken(creds oidc.ClientCredentials) (*jose.JWT, error) { ok, err := s.ClientIdentityRepo.Authenticate(creds) if err != nil { log.Errorf("Failed fetching client %s from repo: %v", creds.ID, err) return nil, oauth2.NewError(oauth2.ErrorServerError) } if !ok { return nil, oauth2.NewError(oauth2.ErrorInvalidClient) } signer, err := s.KeyManager.Signer() if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, oauth2.NewError(oauth2.ErrorServerError) } now := time.Now() exp := now.Add(s.SessionManager.ValidityWindow) claims := oidc.NewClaims(s.IssuerURL.String(), creds.ID, creds.ID, now, exp) claims.Add("name", creds.ID) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, oauth2.NewError(oauth2.ErrorServerError) } log.Infof("Client token sent: clientID=%s", creds.ID) return jwt, nil }
func TestGetClientIDFromAuthorizedRequest(t *testing.T) { now := time.Now() tomorrow := now.Add(24 * time.Hour) privKey, err := key.GeneratePrivateKey() if err != nil { t.Fatalf("Failed to generate private key, error=%v", err) } signer := privKey.Signer() makeToken := func(iss, sub, aud string, iat, exp time.Time) string { claims := oidc.NewClaims(iss, sub, aud, iat, exp) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { t.Fatalf("Failed to generate JWT, error=%v", err) } return jwt.Encode() } tests := []struct { header string wantClient string wantErr bool }{ { header: fmt.Sprintf("BEARER %s", makeToken("iss", "CLIENT_ID", "", now, tomorrow)), wantClient: "CLIENT_ID", wantErr: false, }, { header: fmt.Sprintf("BEARER %s", makeToken("iss", "", "", now, tomorrow)), wantErr: true, }, } for i, tt := range tests { req := &http.Request{ Header: http.Header{ "Authorization": []string{tt.header}, }, } gotClient, err := getClientIDFromAuthorizedRequest(req) if tt.wantErr { if err == nil { t.Errorf("case %d: want non-nil err", i) } continue } if err != nil { t.Errorf("case %d: got err: %q", i, err) continue } if gotClient != tt.wantClient { t.Errorf("case %d: want=%v, got=%v", i, tt.wantClient, gotClient) } } }
func NewInvitation(user User, password Password, issuer url.URL, clientID string, callback url.URL, expires time.Duration) Invitation { claims := oidc.NewClaims(issuer.String(), user.ID, clientID, clock.Now(), clock.Now().Add(expires)) claims.Add(ClaimPasswordResetPassword, string(password)) claims.Add(ClaimEmailVerificationEmail, user.Email) claims.Add(ClaimInvitationCallback, callback.String()) return Invitation{claims} }
// Claims returns a new set of Claims for the current session. // The "sub" of the returned Claims is that of the dex User, not whatever // remote Identity was used to authenticate. func (s *Session) Claims(issuerURL string) jose.Claims { claims := oidc.NewClaims(issuerURL, s.UserID, s.ClientID, s.CreatedAt, s.ExpiresAt) if s.Nonce != "" { claims["nonce"] = s.Nonce } return claims }
// NewEmailVerification creates an object which can be sent to a user in serialized form to verify that they control an email address. // The clientID is the ID of the registering user. The callback is where a user should land after verifying their email. func NewEmailVerification(user User, clientID string, issuer url.URL, callback url.URL, expires time.Duration) EmailVerification { claims := oidc.NewClaims(issuer.String(), user.ID, clientID, clock.Now(), clock.Now().Add(expires)) claims.Add(ClaimEmailVerificationCallback, callback.String()) claims.Add(ClaimEmailVerificationEmail, user.Email) return EmailVerification{claims} }
// Claims returns a new set of Claims for the current session. // The "sub" of the returned Claims is that of the dex User, not whatever // remote Identity was used to authenticate. func (s *Session) Claims(issuerURL string) jose.Claims { claims := oidc.NewClaims(issuerURL, s.UserID, s.ClientID, s.CreatedAt, s.ExpiresAt) if s.Nonce != "" { claims["nonce"] = s.Nonce } if s.Scope.HasScope(scope.ScopeGroups) { claims["groups"] = s.Groups } return claims }
func makeUserToken(issuerURL url.URL, userID, clientID string, expires time.Duration, privKey *key.PrivateKey) string { signer := key.NewPrivateKeySet([]*key.PrivateKey{testPrivKey}, time.Now().Add(time.Minute)).Active().Signer() claims := oidc.NewClaims(issuerURL.String(), userID, clientID, time.Now(), time.Now().Add(expires)) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { panic(fmt.Sprintf("could not make token: %v", err)) } return jwt.Encode() }
func (s *Server) RefreshToken(creds oidc.ClientCredentials, token string) (*jose.JWT, error) { ok, err := s.ClientIdentityRepo.Authenticate(creds) if err != nil { log.Errorf("Failed fetching client %s from repo: %v", creds.ID, err) return nil, oauth2.NewError(oauth2.ErrorServerError) } if !ok { log.Errorf("Failed to Authenticate client %s", creds.ID) return nil, oauth2.NewError(oauth2.ErrorInvalidClient) } userID, err := s.RefreshTokenRepo.Verify(creds.ID, token) switch err { case nil: break case refresh.ErrorInvalidToken: return nil, oauth2.NewError(oauth2.ErrorInvalidRequest) case refresh.ErrorInvalidClientID: return nil, oauth2.NewError(oauth2.ErrorInvalidClient) default: return nil, oauth2.NewError(oauth2.ErrorServerError) } user, err := s.UserRepo.Get(nil, userID) if err != nil { // The error can be user.ErrorNotFound, but we are not deleting // user at this moment, so this shouldn't happen. log.Errorf("Failed to fetch user %q from repo: %v: ", userID, err) return nil, oauth2.NewError(oauth2.ErrorServerError) } signer, err := s.KeyManager.Signer() if err != nil { log.Errorf("Failed to refresh ID token: %v", err) return nil, oauth2.NewError(oauth2.ErrorServerError) } now := time.Now() expireAt := now.Add(session.DefaultSessionValidityWindow) claims := oidc.NewClaims(s.IssuerURL.String(), user.ID, creds.ID, now, expireAt) user.AddToClaims(claims) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, oauth2.NewError(oauth2.ErrorServerError) } log.Infof("New token sent: clientID=%s", creds.ID) return jwt, nil }
func (op *oidcProvider) generateToken(t *testing.T, iss, sub, aud string, usernameClaim, value string, iat, exp time.Time) string { signer := op.privKey.Signer() claims := oidc.NewClaims(iss, sub, aud, iat, exp) claims.Add(usernameClaim, value) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { t.Fatalf("Cannot generate token: %v", err) return "" } return jwt.Encode() }
func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}, iat, exp time.Time) string { signer := op.PrivKey.Signer() claims := oidc.NewClaims(iss, sub, aud, iat, exp) claims.Add(usernameClaim, value) if groups != nil && groupsClaim != "" { claims.Add(groupsClaim, groups) } jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { t.Fatalf("Cannot generate token: %v", err) return "" } return jwt.Encode() }
func NewPasswordReset(user User, password Password, issuer url.URL, clientID string, callback url.URL, expires time.Duration) PasswordReset { claims := oidc.NewClaims(issuer.String(), user.ID, clientID, clock.Now(), clock.Now().Add(expires)) claims.Add(ClaimPasswordResetCallback, callback.String()) claims.Add(ClaimPasswordResetPassword, string(password)) return PasswordReset{claims} }
func TestClientToken(t *testing.T) { now := time.Now() tomorrow := now.Add(24 * time.Hour) validClientID := "valid-client" ci := oidc.ClientIdentity{ Credentials: oidc.ClientCredentials{ ID: validClientID, Secret: base64.URLEncoding.EncodeToString([]byte("secret")), }, Metadata: oidc.ClientMetadata{ RedirectURIs: []url.URL{ {Scheme: "https", Host: "authn.example.com", Path: "/callback"}, }, }, } repo, err := db.NewClientIdentityRepoFromClients(db.NewMemDB(), []oidc.ClientIdentity{ci}) if err != nil { t.Fatalf("Failed to create client identity repo: %v", err) } privKey, err := key.GeneratePrivateKey() if err != nil { t.Fatalf("Failed to generate private key, error=%v", err) } signer := privKey.Signer() pubKey := *key.NewPublicKey(privKey.JWK()) validIss := "https://example.com" makeToken := func(iss, sub, aud string, iat, exp time.Time) string { claims := oidc.NewClaims(iss, sub, aud, iat, exp) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { t.Fatalf("Failed to generate JWT, error=%v", err) } return jwt.Encode() } validJWT := makeToken(validIss, validClientID, validClientID, now, tomorrow) invalidJWT := makeToken("", "", "", now, tomorrow) tests := []struct { keys []key.PublicKey repo client.ClientIdentityRepo header string wantCode int }{ // valid token { keys: []key.PublicKey{pubKey}, repo: repo, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusOK, }, // invalid token { keys: []key.PublicKey{pubKey}, repo: repo, header: fmt.Sprintf("BEARER %s", invalidJWT), wantCode: http.StatusUnauthorized, }, // empty header { keys: []key.PublicKey{pubKey}, repo: repo, header: "", wantCode: http.StatusUnauthorized, }, // unparsable token { keys: []key.PublicKey{pubKey}, repo: repo, header: "BEARER xxx", wantCode: http.StatusUnauthorized, }, // no verification keys { keys: []key.PublicKey{}, repo: repo, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // nil repo { keys: []key.PublicKey{pubKey}, repo: nil, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // empty repo { keys: []key.PublicKey{pubKey}, repo: db.NewClientIdentityRepo(db.NewMemDB()), header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // client not in repo { keys: []key.PublicKey{pubKey}, repo: repo, header: fmt.Sprintf("BEARER %s", makeToken(validIss, "DOESNT-EXIST", "DOESNT-EXIST", now, tomorrow)), wantCode: http.StatusUnauthorized, }, } for i, tt := range tests { w := httptest.NewRecorder() mw := &clientTokenMiddleware{ issuerURL: validIss, ciRepo: tt.repo, keysFunc: func() ([]key.PublicKey, error) { return tt.keys, nil }, next: staticHandler{}, } req := &http.Request{ Header: http.Header{ "Authorization": []string{tt.header}, }, } mw.ServeHTTP(w, req) if tt.wantCode != w.Code { t.Errorf("case %d: invalid response code, want=%d, got=%d", i, tt.wantCode, w.Code) } } }
func (s *Server) RefreshToken(creds oidc.ClientCredentials, scopes scope.Scopes, token string) (*jose.JWT, string, time.Time, error) { ok, err := s.ClientManager.Authenticate(creds) if err != nil { log.Errorf("Failed fetching client %s from repo: %v", creds.ID, err) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } if !ok { log.Errorf("Failed to Authenticate client %s", creds.ID) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorInvalidClient) } userID, connectorID, rtScopes, err := s.RefreshTokenRepo.Verify(creds.ID, token) switch err { case nil: break case refresh.ErrorInvalidToken: return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorInvalidRequest) case refresh.ErrorInvalidClientID: return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorInvalidClient) default: return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } if len(scopes) == 0 { scopes = rtScopes } else { if !rtScopes.Contains(scopes) { return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorInvalidRequest) } } usr, err := s.UserRepo.Get(nil, userID) if err != nil { // The error can be user.ErrorNotFound, but we are not deleting // user at this moment, so this shouldn't happen. log.Errorf("Failed to fetch user %q from repo: %v: ", userID, err) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } var groups []string if rtScopes.HasScope(scope.ScopeGroups) { conn, ok := s.connector(connectorID) if !ok { log.Errorf("refresh token contained invalid connector ID (%s)", connectorID) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } grouper, ok := conn.(connector.GroupsConnector) if !ok { log.Errorf("refresh token requested groups for connector (%s) that doesn't support groups", connectorID) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } remoteIdentities, err := s.UserRepo.GetRemoteIdentities(nil, userID) if err != nil { log.Errorf("failed to get remote identities: %v", err) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } remoteIdentity, ok := func() (user.RemoteIdentity, bool) { for _, ri := range remoteIdentities { if ri.ConnectorID == connectorID { return ri, true } } return user.RemoteIdentity{}, false }() if !ok { log.Errorf("failed to get remote identity for connector %s", connectorID) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } if groups, err = grouper.Groups(remoteIdentity.ID); err != nil { log.Errorf("failed to get groups for refresh token: %v", connectorID) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } } signer, err := s.KeyManager.Signer() if err != nil { log.Errorf("Failed to refresh ID token: %v", err) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } now := time.Now() expiresAt := now.Add(session.DefaultSessionValidityWindow) claims := oidc.NewClaims(s.IssuerURL.String(), usr.ID, creds.ID, now, expiresAt) usr.AddToClaims(claims) if rtScopes.HasScope(scope.ScopeGroups) { if groups == nil { groups = []string{} } claims["groups"] = groups } s.addClaimsFromScope(claims, scope.Scopes(scopes), creds.ID) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { log.Errorf("Failed to generate ID token: %v", err) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } refreshToken, err := s.RefreshTokenRepo.RenewRefreshToken(creds.ID, userID, token) if err != nil { log.Errorf("Failed to generate new refresh token: %v", err) return nil, "", time.Time{}, oauth2.NewError(oauth2.ErrorServerError) } log.Infof("New token sent: clientID=%s", creds.ID) return jwt, refreshToken, expiresAt, nil }
func TestHandleVerifyEmailResend(t *testing.T) { now := time.Now() tomorrow := now.Add(24 * time.Hour) yesterday := now.Add(-24 * time.Hour) privKey, err := key.GeneratePrivateKey() if err != nil { t.Fatalf("Failed to generate private key, error=%v", err) } signer := privKey.Signer() pubKey := *key.NewPublicKey(privKey.JWK()) keysFunc := func() ([]key.PublicKey, error) { return []key.PublicKey{pubKey}, nil } makeToken := func(iss, sub, aud string, iat, exp time.Time) string { claims := oidc.NewClaims(iss, sub, aud, iat, exp) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { t.Fatalf("Failed to generate JWT, error=%v", err) } return jwt.Encode() } tests := []struct { bearerJWT string userJWT string redirectURL url.URL wantCode int verifyEmailUserID string }{ { // The happy case bearerJWT: makeToken(testIssuerURL.String(), testClientID, testClientID, now, tomorrow), userJWT: makeToken(testIssuerURL.String(), "ID-1", testClientID, now, tomorrow), redirectURL: testRedirectURL, wantCode: http.StatusOK, }, { // Already verified bearerJWT: makeToken(testIssuerURL.String(), testClientID, testClientID, now, tomorrow), userJWT: makeToken(testIssuerURL.String(), "ID-1", testClientID, now, tomorrow), redirectURL: testRedirectURL, wantCode: http.StatusBadRequest, verifyEmailUserID: "ID-1", }, { // Expired userJWT bearerJWT: makeToken(testIssuerURL.String(), testClientID, testClientID, now, tomorrow), userJWT: makeToken(testIssuerURL.String(), "ID-1", testClientID, now, yesterday), redirectURL: testRedirectURL, wantCode: http.StatusUnauthorized, }, { // Client ID is unknown bearerJWT: makeToken(testIssuerURL.String(), "fakeclientid", testClientID, now, tomorrow), userJWT: makeToken(testIssuerURL.String(), "ID-1", testClientID, now, tomorrow), redirectURL: testRedirectURL, wantCode: http.StatusBadRequest, }, { // No sub in user JWT bearerJWT: makeToken(testIssuerURL.String(), testClientID, testClientID, now, tomorrow), userJWT: makeToken(testIssuerURL.String(), "", testClientID, now, tomorrow), redirectURL: testRedirectURL, wantCode: http.StatusBadRequest, }, { // Unknown user bearerJWT: makeToken(testIssuerURL.String(), testClientID, testClientID, now, tomorrow), userJWT: makeToken(testIssuerURL.String(), "NonExistent", testClientID, now, tomorrow), redirectURL: testRedirectURL, wantCode: http.StatusBadRequest, }, { // No redirect URL bearerJWT: makeToken(testIssuerURL.String(), testClientID, testClientID, now, tomorrow), userJWT: makeToken(testIssuerURL.String(), "ID-1", testClientID, now, tomorrow), redirectURL: url.URL{}, wantCode: http.StatusBadRequest, }, } for i, tt := range tests { f, err := makeTestFixtures() if tt.verifyEmailUserID != "" { usr, _ := f.userRepo.Get(nil, tt.verifyEmailUserID) usr.EmailVerified = true f.userRepo.Update(nil, usr) } if err != nil { t.Fatalf("case %d: could not make test fixtures: %v", i, err) } hdlr := handleVerifyEmailResendFunc( testIssuerURL, keysFunc, f.srv.UserEmailer, f.userRepo, f.clientManager) w := httptest.NewRecorder() u := "http://example.com" q := struct { Token string `json:"token"` RedirectURI string `json:"redirectURI"` }{ Token: tt.userJWT, RedirectURI: tt.redirectURL.String(), } qBytes, err := json.Marshal(&q) if err != nil { t.Errorf("case %d: unable to marshal JSON: %q", i, err) } req, err := http.NewRequest("POST", u, bytes.NewReader(qBytes)) req.Header.Set("Authorization", "Bearer "+tt.bearerJWT) if err != nil { t.Errorf("case %d: unable to form HTTP request: %v", i, err) } hdlr.ServeHTTP(w, req) if tt.wantCode != w.Code { t.Errorf("case %d: wantCode=%v, got=%v", i, tt.wantCode, w.Code) t.Logf("case %d: response body was: %v", i, w.Body.String()) } } }
func TestClientToken(t *testing.T) { now := time.Now() tomorrow := now.Add(24 * time.Hour) clientMetadata := oidc.ClientMetadata{ RedirectURIs: []url.URL{ {Scheme: "https", Host: "authn.example.com", Path: "/callback"}, }, } dbm := db.NewMemDB() clientRepo := db.NewClientRepo(dbm) clientManager := clientmanager.NewClientManager(clientRepo, db.TransactionFactory(dbm), clientmanager.ManagerOptions{}) cli := client.Client{ Metadata: clientMetadata, } creds, err := clientManager.New(cli) if err != nil { t.Fatalf("Failed to create client: %v", err) } validClientID := creds.ID privKey, err := key.GeneratePrivateKey() if err != nil { t.Fatalf("Failed to generate private key, error=%v", err) } signer := privKey.Signer() pubKey := *key.NewPublicKey(privKey.JWK()) validIss := "https://example.com" makeToken := func(iss, sub, aud string, iat, exp time.Time) string { claims := oidc.NewClaims(iss, sub, aud, iat, exp) jwt, err := jose.NewSignedJWT(claims, signer) if err != nil { t.Fatalf("Failed to generate JWT, error=%v", err) } return jwt.Encode() } validJWT := makeToken(validIss, validClientID, validClientID, now, tomorrow) invalidJWT := makeToken("", "", "", now, tomorrow) tests := []struct { keys []key.PublicKey manager *clientmanager.ClientManager header string wantCode int }{ // valid token { keys: []key.PublicKey{pubKey}, manager: clientManager, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusOK, }, // invalid token { keys: []key.PublicKey{pubKey}, manager: clientManager, header: fmt.Sprintf("BEARER %s", invalidJWT), wantCode: http.StatusUnauthorized, }, // empty header { keys: []key.PublicKey{pubKey}, manager: clientManager, header: "", wantCode: http.StatusUnauthorized, }, // unparsable token { keys: []key.PublicKey{pubKey}, manager: clientManager, header: "BEARER xxx", wantCode: http.StatusUnauthorized, }, // no verification keys { keys: []key.PublicKey{}, manager: clientManager, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // nil repo { keys: []key.PublicKey{pubKey}, manager: nil, header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // empty repo { keys: []key.PublicKey{pubKey}, manager: clientmanager.NewClientManager(db.NewClientRepo(db.NewMemDB()), db.TransactionFactory(db.NewMemDB()), clientmanager.ManagerOptions{}), header: fmt.Sprintf("BEARER %s", validJWT), wantCode: http.StatusUnauthorized, }, // client not in repo { keys: []key.PublicKey{pubKey}, manager: clientManager, header: fmt.Sprintf("BEARER %s", makeToken(validIss, "DOESNT-EXIST", "DOESNT-EXIST", now, tomorrow)), wantCode: http.StatusUnauthorized, }, } for i, tt := range tests { w := httptest.NewRecorder() mw := &clientTokenMiddleware{ issuerURL: validIss, ciManager: tt.manager, keysFunc: func() ([]key.PublicKey, error) { return tt.keys, nil }, next: staticHandler{}, } req := &http.Request{ Header: http.Header{ "Authorization": []string{tt.header}, }, } mw.ServeHTTP(w, req) if tt.wantCode != w.Code { t.Errorf("case %d: invalid response code, want=%d, got=%d", i, tt.wantCode, w.Code) } } }