From 553e7d01670754c1b874d0aa992a57eaa055423c Mon Sep 17 00:00:00 2001 From: Eric Chiang Date: Wed, 6 Apr 2016 11:29:33 -0700 Subject: [PATCH] server: add refresh token revocation API to server --- server/config.go | 2 ++ server/server.go | 8 +++---- server/user.go | 54 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/server/config.go b/server/config.go index 243d1a00..05d20d77 100644 --- a/server/config.go +++ b/server/config.go @@ -160,6 +160,7 @@ func (cfg *SingleServerConfig) Configure(srv *Server) error { srv.SessionManager = sm srv.RefreshTokenRepo = refTokRepo srv.HealthChecks = append(srv.HealthChecks, db.NewHealthChecker(dbMap)) + srv.dbMap = dbMap return nil } @@ -253,6 +254,7 @@ func (cfg *MultiServerConfig) Configure(srv *Server) error { srv.SessionManager = sm srv.RefreshTokenRepo = refreshTokenRepo srv.HealthChecks = append(srv.HealthChecks, db.NewHealthChecker(dbc)) + srv.dbMap = dbc return nil } diff --git a/server/server.go b/server/server.go index a8f61f42..5694320b 100644 --- a/server/server.go +++ b/server/server.go @@ -15,6 +15,7 @@ import ( "github.com/coreos/go-oidc/oauth2" "github.com/coreos/go-oidc/oidc" "github.com/coreos/pkg/health" + "github.com/go-gorp/gorp" "github.com/jonboulle/clockwork" "github.com/coreos/dex/client" @@ -77,6 +78,7 @@ type Server struct { EnableRegistration bool EnableClientRegistration bool + dbMap *gorp.DbMap localConnectorID string } @@ -270,12 +272,10 @@ func (s *Server) HTTPHandler() http.Handler { clientPath, clientHandler := registerClientResource(apiBasePath, s.ClientIdentityRepo) mux.Handle(path.Join(apiBasePath, clientPath), s.NewClientTokenAuthHandler(clientHandler)) - usersAPI := usersapi.NewUsersAPI(s.UserManager, s.ClientIdentityRepo, s.UserEmailer, s.localConnectorID) + usersAPI := usersapi.NewUsersAPI(s.dbMap, s.UserManager, s.UserEmailer, s.localConnectorID) handler := NewUserMgmtServer(usersAPI, s.JWTVerifierFactory(), s.UserManager, s.ClientIdentityRepo).HTTPHandler() - path := path.Join(apiBasePath, UsersSubTree) - mux.Handle(path, handler) - mux.Handle(path+"/", handler) + mux.Handle(apiBasePath+"/", handler) return http.Handler(mux) } diff --git a/server/user.go b/server/user.go index 8481ee3f..964afe91 100644 --- a/server/user.go +++ b/server/user.go @@ -30,6 +30,9 @@ var ( UsersGetEndpoint = addBasePath(UsersSubTree + "/:id") UsersDisableEndpoint = addBasePath(UsersSubTree + "/:id/disable") UsersResendInvitationEndpoint = addBasePath(UsersSubTree + "/:id/resend-invitation") + AccountSubTree = "/account" + AccountListRefreshTokens = addBasePath(AccountSubTree + "/:userid/refresh") + AccountRevokeRefreshToken = addBasePath(AccountSubTree + "/:userid/refresh/:clientid") ) type UserMgmtServer struct { @@ -52,26 +55,48 @@ func (s *UserMgmtServer) HTTPHandler() http.Handler { r := httprouter.New() r.RedirectTrailingSlash = false r.RedirectFixedPath = false - r.GET(UsersListEndpoint, s.authAPIHandle(s.listUsers)) - r.POST(UsersCreateEndpoint, s.authAPIHandle(s.createUser)) - r.POST(UsersDisableEndpoint, s.authAPIHandle(s.disableUser)) - r.GET(UsersGetEndpoint, s.authAPIHandle(s.getUser)) - r.POST(UsersResendInvitationEndpoint, s.authAPIHandle(s.resendInvitationEmail)) + + r.GET(UsersListEndpoint, s.authAdminUser(s.listUsers)) + r.POST(UsersCreateEndpoint, s.authAdminUser(s.createUser)) + r.POST(UsersDisableEndpoint, s.authAdminUser(s.disableUser)) + r.GET(UsersGetEndpoint, s.authAdminUser(s.getUser)) + r.POST(UsersResendInvitationEndpoint, s.authAdminUser(s.resendInvitationEmail)) + + r.GET(AccountListRefreshTokens, s.authAccount(s.listClientsWithRefreshTokens)) + r.DELETE(AccountRevokeRefreshToken, s.authAccount(s.revokeRefreshTokensForClient)) return r } +func (s *UserMgmtServer) authAdminUser(handle authedHandle) httprouter.Handle { + return s.authAPIHandle(handle, true) +} + +func (s *UserMgmtServer) authAccount(handle authedHandle) httprouter.Handle { + return s.authAPIHandle(handle, false) +} + // authedHandle is an HTTP handle which requires requests to be authenticated as an admin user. type authedHandle func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, creds api.Creds) // authAPIHandle is a middleware function with authenticates an HTTP request before passing // it along to the authedHandle. -func (s *UserMgmtServer) authAPIHandle(handle authedHandle) httprouter.Handle { +// +// The authorization checks for an ID token bearer token in the request header, requiring the +// audience (aud claim) be a client ID of an admin client. +// +// If requiresAdmin is true, the subject identifier (sub claim) of the ID token provided must be +// that of an admin user. +func (s *UserMgmtServer) authAPIHandle(handle authedHandle, requiresAdmin bool) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { creds, err := s.getCreds(r) if err != nil { s.writeError(w, err) return } + if creds.User.Disabled || (requiresAdmin && !creds.User.Admin) { + s.writeError(w, api.ErrorUnauthorized) + return + } handle(w, r, ps, creds) } } @@ -191,6 +216,23 @@ func (s *UserMgmtServer) resendInvitationEmail(w http.ResponseWriter, r *http.Re writeResponseWithBody(w, http.StatusOK, resendEmailInvitationResponse) } +func (s *UserMgmtServer) listClientsWithRefreshTokens(w http.ResponseWriter, r *http.Request, ps httprouter.Params, creds api.Creds) { + clients, err := s.api.ListClientsWithRefreshTokens(creds, ps.ByName("userid")) + if err != nil { + s.writeError(w, err) + return + } + writeResponseWithBody(w, http.StatusOK, schema.RefreshClientList{Clients: clients}) +} + +func (s *UserMgmtServer) revokeRefreshTokensForClient(w http.ResponseWriter, r *http.Request, ps httprouter.Params, creds api.Creds) { + if err := s.api.RevokeRefreshTokensForClient(creds, ps.ByName("userid"), ps.ByName("clientid")); err != nil { + s.writeError(w, err) + return + } + w.WriteHeader(http.StatusOK) // NOTE (ericchiang): http.StatusNoContent or return an empty JSON object? +} + func (s *UserMgmtServer) writeError(w http.ResponseWriter, err error) { log.Errorf("Error calling user management API: %v: ", err) if apiErr, ok := err.(api.Error); ok {