func makeUserAPITestFixtures() *userAPITestFixtures { f := &userAPITestFixtures{} _, _, um := makeUserObjects(userUsers, userPasswords) cir := client.NewClientIdentityRepo([]oidc.ClientIdentity{ oidc.ClientIdentity{ Credentials: oidc.ClientCredentials{ ID: testClientID, Secret: testClientSecret, }, Metadata: oidc.ClientMetadata{ RedirectURLs: []url.URL{ testRedirectURL, }, }, }, oidc.ClientIdentity{ Credentials: oidc.ClientCredentials{ ID: userBadClientID, Secret: "secret", }, Metadata: oidc.ClientMetadata{ RedirectURLs: []url.URL{ testRedirectURL, }, }, }, }) cir.SetDexAdmin(testClientID, true) noop := func() error { return nil } keysFunc := func() []key.PublicKey { return []key.PublicKey{*key.NewPublicKey(testPrivKey.JWK())} } jwtvFactory := func(clientID string) oidc.JWTVerifier { return oidc.NewJWTVerifier(testIssuerURL.String(), clientID, noop, keysFunc) } f.emailer = &testEmailer{} api := api.NewUsersAPI(um, cir, f.emailer, "local") usrSrv := server.NewUserMgmtServer(api, jwtvFactory, um, cir) f.hSrv = httptest.NewServer(usrSrv.HTTPHandler()) f.trans = &tokenHandlerTransport{ Handler: usrSrv.HTTPHandler(), Token: userGoodToken, } hc := &http.Client{ Transport: f.trans, } f.client, _ = schema.NewWithBasePath(hc, f.hSrv.URL) return f }
func (c *Client) VerifyJWTForClientID(jwt jose.JWT, clientID string) error { var keysFunc func() []key.PublicKey if kID, ok := jwt.KeyID(); ok { keysFunc = c.keysFuncWithID(kID) } else { keysFunc = c.keysFuncAll() } v := oidc.NewJWTVerifier( c.providerConfig.Get().Issuer.String(), clientID, c.maybeSyncKeys, keysFunc) return v.Verify(jwt) }
func (s *Server) JWTVerifierFactory() JWTVerifierFactory { noop := func() error { return nil } keyFunc := func() []key.PublicKey { keys, err := s.KeyManager.PublicKeys() if err != nil { log.Errorf("error getting public keys from manager: %v", err) return []key.PublicKey{} } return keys } return func(clientID string) oidc.JWTVerifier { return oidc.NewJWTVerifier(s.IssuerURL.String(), clientID, noop, keyFunc) } }
// Returns TokenClaims if and only if // - the given token string is an appropriately formatted JWT // - the JWT contains nonempty "aud" and "sub" claims // - the JWT can be verified for the client associated with the "aud" claim // using the given keys func parseAndVerifyTokenClaims(token string, issuer url.URL, keys []key.PublicKey) (TokenClaims, error) { jwt, err := jose.ParseJWT(token) if err != nil { return TokenClaims{}, err } claims, err := jwt.Claims() if err != nil { return TokenClaims{}, err } clientID, ok, err := claims.StringClaim("aud") if err != nil { return TokenClaims{}, err } if !ok || clientID == "" { return TokenClaims{}, errors.New("no aud(client ID) claim") } sub, ok, err := claims.StringClaim("sub") if err != nil { return TokenClaims{}, err } if !ok || sub == "" { return TokenClaims{}, errors.New("no sub claim") } noop := func() error { return nil } keysFunc := func() []key.PublicKey { return keys } verifier := oidc.NewJWTVerifier(issuer.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { return TokenClaims{}, err } timeClaimsToInt(claims) return TokenClaims{claims}, nil }
// ParseAndVerifyPasswordResetToken parses a string into a an PasswordReset, verifies the signature, and ensures that required claims are present. // In addition to the usual claims required by the OIDC spec, "aud" and "sub" must be present as well as ClaimPasswordResetCallback, ClaimPasswordResetEmail and ClaimPasswordResetPassword. func ParseAndVerifyPasswordResetToken(token string, issuer url.URL, keys []key.PublicKey) (PasswordReset, error) { jwt, err := jose.ParseJWT(token) if err != nil { return PasswordReset{}, err } claims, err := jwt.Claims() if err != nil { return PasswordReset{}, err } cb, ok, err := claims.StringClaim(ClaimPasswordResetCallback) if err != nil { return PasswordReset{}, err } var clientID string if ok && cb != "" { clientID, ok, err = claims.StringClaim("aud") if err != nil { return PasswordReset{}, err } if !ok || clientID == "" { return PasswordReset{}, errors.New("no aud(client ID) claim") } } if _, err := url.Parse(cb); err != nil { return PasswordReset{}, fmt.Errorf("callback URL not parseable: %v", cb) } pw, ok, err := claims.StringClaim(ClaimPasswordResetPassword) if err != nil { return PasswordReset{}, err } if pw == "" { return PasswordReset{}, fmt.Errorf("no %q claim", ClaimPasswordResetPassword) } sub, ok, err := claims.StringClaim("sub") if err != nil { return PasswordReset{}, err } if sub == "" { return PasswordReset{}, errors.New("no sub claim") } noop := func() error { return nil } keysFunc := func() []key.PublicKey { return keys } verifier := oidc.NewJWTVerifier(issuer.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { return PasswordReset{}, err } return PasswordReset{ claims: claims, }, nil }
// handleVerifyEmailResendFunc will resend an email-verification email given a valid JWT for the user and a redirect URL. // This handler is meant to be wrapped in clientTokenMiddleware, so a valid // bearer token for the client is expected to be present. // The user's JWT should be in the "token" parameter and the redirect URL should // be in the "redirect_uri" param. Note that this re func handleVerifyEmailResendFunc( issuerURL url.URL, srvKeysFunc func() ([]key.PublicKey, error), emailer *useremail.UserEmailer, userRepo user.UserRepo, clientIdentityRepo client.ClientIdentityRepo) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var params struct { Token string `json:"token"` RedirectURI string `json:"redirectURI"` } err := decoder.Decode(¶ms) if err != nil { writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "unable to parse body as JSON")) return } token := params.Token if token == "" { writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "missing valid JWT")) return } clientID, err := getClientIDFromAuthorizedRequest(r) if err != nil { log.Errorf("Failed to extract clientID: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorInvalidRequest, "cilent could not be extracted from bearer token.")) return } cm, err := clientIdentityRepo.Metadata(clientID) if err == client.ErrorNotFound { log.Errorf("No such client: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid client_id")) return } if err != nil { log.Errorf("Error getting ClientMetadata: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } noop := func() error { return nil } keysFunc := func() []key.PublicKey { keys, err := srvKeysFunc() if err != nil { log.Errorf("Error getting keys: %v", err) } return keys } jwt, err := jose.ParseJWT(token) if err != nil { log.Errorf("Failed to Parse JWT: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "token could not be parsed")) return } verifier := oidc.NewJWTVerifier(issuerURL.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { log.Errorf("Failed to Verify JWT: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorAccessDenied, "invalid token could not be verified")) return } claims, err := jwt.Claims() if err != nil { log.Errorf("Failed to extract claims from JWT: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid token could not be parsed")) return } sub, ok, err := claims.StringClaim("sub") if err != nil || !ok || sub == "" { log.Errorf("Failed to extract sub claim from JWT: err:%q ok:%v", err, ok) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "could not extract sub claim from token")) return } usr, err := userRepo.Get(nil, sub) if err != nil { if err == user.ErrorNotFound { log.Errorf("Failed to find user specified by token: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "could not find user")) return } log.Errorf("Failed to fetch user: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } if usr.EmailVerified { log.Errorf("User's email already verified") writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "email already verified")) return } aud, _, _ := claims.StringClaim("aud") if aud != clientID { log.Errorf("aud of token and sub of bearer token must match: %v", err) writeAPIError(w, http.StatusForbidden, newAPIError(errorAccessDenied, "JWT is from another client.")) return } redirectURLStr := params.RedirectURI if redirectURLStr == "" { log.Errorf("No redirect URL: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "must provide a redirect_uri")) return } redirectURL, err := url.Parse(redirectURLStr) if err != nil { log.Errorf("Unparsable URL: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return } *redirectURL, err = client.ValidRedirectURL(redirectURL, cm.RedirectURLs) if err != nil { switch err { case (client.ErrorInvalidRedirectURL): log.Errorf("Request provided unregistered redirect URL: %s", redirectURLStr) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return case (client.ErrorNoValidRedirectURLs): log.Errorf("There are no registered URLs for the requested client: %s", redirectURL) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return } } _, err = emailer.SendEmailVerification(usr.ID, clientID, *redirectURL) if err != nil { log.Errorf("Failed to send email verification email: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } writeResponseWithBody(w, http.StatusOK, struct{}{}) } }
func makeUserAPITestFixtures(clientCredsFlag bool) *userAPITestFixtures { f := &userAPITestFixtures{} dbMap, _, _, um := makeUserObjects(userUsers, userPasswords) clients := []client.LoadableClient{ { Client: client.Client{ Credentials: oidc.ClientCredentials{ ID: testClientID, Secret: testClientSecret, }, Metadata: oidc.ClientMetadata{ RedirectURIs: []url.URL{ testRedirectURL, }, }, }, }, { Client: client.Client{ Credentials: oidc.ClientCredentials{ ID: userBadClientID, Secret: base64.URLEncoding.EncodeToString([]byte("secret")), }, Metadata: oidc.ClientMetadata{ RedirectURIs: []url.URL{ testBadRedirectURL, }, }, }, }, } _, clientManager, err := makeClientRepoAndManager(dbMap, clients) if err != nil { panic("Failed to create client identity manager: " + err.Error()) } clientManager.SetDexAdmin(testClientID, true) noop := func() error { return nil } keysFunc := func() []key.PublicKey { return []key.PublicKey{*key.NewPublicKey(testPrivKey.JWK())} } jwtvFactory := func(clientID string) oidc.JWTVerifier { return oidc.NewJWTVerifier(testIssuerURL.String(), clientID, noop, keysFunc) } refreshRepo := db.NewRefreshTokenRepo(dbMap) for _, user := range userUsers { if _, err := refreshRepo.Create(user.User.ID, testClientID, "", append([]string{"offline_access"}, oidc.DefaultScope...)); err != nil { panic("Failed to create refresh token: " + err.Error()) } } f.emailer = &testEmailer{} um.Clock = clock api := api.NewUsersAPI(um, clientManager, refreshRepo, f.emailer, "local", clientCredsFlag) usrSrv := server.NewUserMgmtServer(api, jwtvFactory, um, clientManager, clientCredsFlag) f.hSrv = httptest.NewServer(usrSrv.HTTPHandler()) f.trans = &tokenHandlerTransport{ Handler: usrSrv.HTTPHandler(), Token: userGoodToken, } hc := &http.Client{ Transport: f.trans, } f.client, _ = schema.NewWithBasePath(hc, f.hSrv.URL) return f }
func makeUserAPITestFixtures() *userAPITestFixtures { f := &userAPITestFixtures{} dbMap, _, _, um := makeUserObjects(userUsers, userPasswords) clients := []client.Client{ client.Client{ Credentials: oidc.ClientCredentials{ ID: testClientID, Secret: testClientSecret, }, Metadata: oidc.ClientMetadata{ RedirectURIs: []url.URL{ testRedirectURL, }, }, }, client.Client{ Credentials: oidc.ClientCredentials{ ID: userBadClientID, Secret: base64.URLEncoding.EncodeToString([]byte("secret")), }, Metadata: oidc.ClientMetadata{ RedirectURIs: []url.URL{ testBadRedirectURL, }, }, }, } clientIDGenerator := func(hostport string) (string, error) { return hostport, nil } secGen := func() ([]byte, error) { return []byte(testClientSecret), nil } clientRepo := db.NewClientRepo(dbMap) clientManager, err := manager.NewClientManagerFromClients(clientRepo, db.TransactionFactory(dbMap), clients, manager.ManagerOptions{ClientIDGenerator: clientIDGenerator, SecretGenerator: secGen}) if err != nil { panic("Failed to create client identity manager: " + err.Error()) } clientManager.SetDexAdmin(testClientID, true) noop := func() error { return nil } keysFunc := func() []key.PublicKey { return []key.PublicKey{*key.NewPublicKey(testPrivKey.JWK())} } jwtvFactory := func(clientID string) oidc.JWTVerifier { return oidc.NewJWTVerifier(testIssuerURL.String(), clientID, noop, keysFunc) } refreshRepo := db.NewRefreshTokenRepo(dbMap) for _, user := range userUsers { if _, err := refreshRepo.Create(user.User.ID, testClientID); err != nil { panic("Failed to create refresh token: " + err.Error()) } } f.emailer = &testEmailer{} um.Clock = clock api := api.NewUsersAPI(um, clientManager, refreshRepo, f.emailer, "local") usrSrv := server.NewUserMgmtServer(api, jwtvFactory, um, clientManager) f.hSrv = httptest.NewServer(usrSrv.HTTPHandler()) f.trans = &tokenHandlerTransport{ Handler: usrSrv.HTTPHandler(), Token: userGoodToken, } hc := &http.Client{ Transport: f.trans, } f.client, _ = schema.NewWithBasePath(hc, f.hSrv.URL) return f }
// ParseAndVerifyEmailVerificationToken parses a string into a an EmailVerification, verifies the signature, and ensures that required claims are present. // In addition to the usual claims required by the OIDC spec, "aud" and "sub" must be present as well as ClaimEmailVerificationCallback and ClaimEmailVerificationEmail. func ParseAndVerifyEmailVerificationToken(token string, issuer url.URL, keys []key.PublicKey) (EmailVerification, error) { jwt, err := jose.ParseJWT(token) if err != nil { return EmailVerification{}, err } claims, err := jwt.Claims() if err != nil { return EmailVerification{}, err } clientID, ok, err := claims.StringClaim("aud") if err != nil { return EmailVerification{}, err } if !ok { return EmailVerification{}, errors.New("no aud(client ID) claim") } cb, ok, err := claims.StringClaim(ClaimEmailVerificationCallback) if err != nil { return EmailVerification{}, err } if cb == "" { return EmailVerification{}, fmt.Errorf("no %q claim", ClaimEmailVerificationCallback) } if _, err := url.Parse(cb); err != nil { return EmailVerification{}, fmt.Errorf("callback URL not parseable: %v", cb) } email, ok, err := claims.StringClaim(ClaimEmailVerificationEmail) if err != nil { return EmailVerification{}, err } if email == "" { return EmailVerification{}, fmt.Errorf("no %q claim", ClaimEmailVerificationEmail) } sub, ok, err := claims.StringClaim("sub") if err != nil { return EmailVerification{}, err } if sub == "" { return EmailVerification{}, errors.New("no sub claim") } noop := func() error { return nil } keysFunc := func() []key.PublicKey { return keys } verifier := oidc.NewJWTVerifier(issuer.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { return EmailVerification{}, err } return EmailVerification{ claims: claims, }, nil }