server: add refresh token revocation API to server

This commit is contained in:
Eric Chiang 2016-04-06 11:29:33 -07:00
parent 64380734e6
commit 553e7d0167
3 changed files with 54 additions and 10 deletions

View file

@ -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
}

View file

@ -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)
}

View file

@ -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 {