//Handler creates a much with handlers for all routes in the roll application func Handler(core *roll.Core) http.Handler { mux := http.NewServeMux() //Wrap roll services with the auth checker if booted in secure mode if core.Secure() { rollClientID := os.Getenv("ROLL_CLIENTID") if rollClientID == "" { panic(errors.New("Cannot run in secure mode without a client ID to white list (from ROLL_CLIENTID env variable)")) } whitelist := []string{rollClientID} mux.Handle(DevelopersBaseURI, authzwrapper.Wrap(core.SecretsRepo, core.AdminRepo, whitelist, handleDevelopersBase(core))) mux.Handle(DevelopersURI, authzwrapper.Wrap(core.SecretsRepo, core.AdminRepo, whitelist, handleDevelopers(core))) mux.Handle(ApplicationsURI, authzwrapper.Wrap(core.SecretsRepo, core.AdminRepo, whitelist, handleApplications(core))) mux.Handle(ApplicationsBaseURI, authzwrapper.Wrap(core.SecretsRepo, core.AdminRepo, whitelist, handleApplicationsBase(core))) mux.Handle(JWTFlowCertsURI, authzwrapper.Wrap(core.SecretsRepo, core.AdminRepo, whitelist, handleJWTFlowCerts(core))) } else { mux.Handle(DevelopersBaseURI, authzwrapper.WrapUnsecure(handleDevelopersBase(core))) mux.Handle(DevelopersURI, authzwrapper.WrapUnsecure(handleDevelopers(core))) mux.Handle(ApplicationsURI, authzwrapper.WrapUnsecure(handleApplications(core))) mux.Handle(ApplicationsBaseURI, authzwrapper.WrapUnsecure(handleApplicationsBase(core))) mux.Handle(JWTFlowCertsURI, authzwrapper.WrapUnsecure(handleJWTFlowCerts(core))) } mux.Handle(AuthorizeBaseURI, handleAuthorize(core)) mux.Handle(ValidateBaseURI, handleValidate(core)) mux.Handle(OAuth2TokenBaseURI, handleToken(core)) mux.Handle(TokenInfoURI, handleTokenInfo(core)) return mux }
func handleGetPublicKey(core *roll.Core, w http.ResponseWriter, r *http.Request) { //Extract client id clientID := strings.TrimPrefix(r.RequestURI, JWTFlowCertsURI) if clientID == "" { respondError(w, http.StatusBadRequest, errors.New("Resource not specified")) return } log.Info("retrieve public key for application: ", clientID) //Retrieve the app definition. Note that here since we are only returning publically //available information, we do not have to apply the data security model app, err := core.SystemRetrieveApplication(clientID) if err != nil { log.Info("error retrieving application") respondError(w, http.StatusInternalServerError, errReadingApplicationRecord) return } if app == nil { log.Info("application not found") respondError(w, http.StatusNotFound, nil) return } pk := publicKeyCtx{ PublicKey: app.JWTFlowPublicKey, } respondOk(w, &pk) }
func retrieveApplication(clientID string, core *roll.Core, w http.ResponseWriter, r *http.Request) { log.Info("ret appl called: ", clientID) if clientID == "" { respondError(w, http.StatusBadRequest, errors.New("Resource not specified")) return } subject, scope, err := subjectAndAdminScopeFromRequestCtx(r) if err != nil { respondError(w, http.StatusInternalServerError, nil) return } app, err := core.RetrieveApplication(clientID, subject, scope) if err != nil { respondError(w, http.StatusInternalServerError, err) return } if app == nil { respondNotFound(w) return } respondOk(w, app) }
func retrieveDeveloper(email string, core *roll.Core, w http.ResponseWriter, r *http.Request) { if !roll.ValidateEmail(email) { respondError(w, http.StatusBadRequest, fmt.Errorf("Invalid email: %s", email)) return } subject, scope, err := subjectAndAdminScopeFromRequestCtx(r) if err != nil { respondError(w, http.StatusInternalServerError, nil) return } dev, err := core.RetrieveDeveloper(email, subject, scope) if err != nil { respondError(w, http.StatusInternalServerError, err) return } if dev == nil { respondNotFound(w) return } respondOk(w, dev) }
func generateSignedCode(core *roll.Core, subject, scope string, app *roll.Application) (string, error) { privateKey, err := core.RetrievePrivateKeyForApp(app.ClientID) if err != nil { return "", err } token, err := rolltoken.GenerateCode(subject, scope, app.ClientID, privateKey) return token, err }
func generateJWT(subject, scope string, core *roll.Core, app *roll.Application) (string, error) { privateKey, err := core.RetrievePrivateKeyForApp(app.ClientID) if err != nil { return "", err } token, err := rolltoken.GenerateToken(subject, scope, app.ClientID, app.ApplicationName, privateKey) return token, err }
func lookupApplicationFromFormClientID(core *roll.Core, r *http.Request) (*roll.Application, error) { app, err := core.SystemRetrieveApplication(r.Form["client_id"][0]) if err != nil { return nil, err } if app == nil { return nil, errors.New("Invalid client id") } return app, nil }
func lookupApplicatioByAudience(core *roll.Core, audience string) (*roll.Application, error) { app, err := core.SystemRetrieveApplicationByJWTFlowAudience(audience) if err != nil { log.Info("Error retrieving app data: ", err.Error()) return nil, ErrRetrievingAppData } if app == nil { log.Info("invalid client id") return nil, errors.New("Invalid client id") } return app, nil }
func lookupApplication(core *roll.Core, clientID string) (*roll.Application, error) { app, err := core.SystemRetrieveApplication(clientID) if err != nil { log.Info("Error retrieving app data: ", err.Error()) return nil, ErrRetrievingAppData } if app == nil { log.Info("Invalid client id: ", clientID) return nil, errors.New("Invalid client id") } return app, nil }
func listApplications(core *roll.Core, w http.ResponseWriter, r *http.Request) { subject, scope, err := subjectAndAdminScopeFromRequestCtx(r) if err != nil { respondError(w, http.StatusInternalServerError, nil) return } apps, err := core.ListApplications(subject, scope) if err != nil { respondError(w, http.StatusInternalServerError, err) return } respondOk(w, apps) }
func validateClientSecret(core *roll.Core, r *http.Request, clientID, clientSecret string) (*roll.Application, error) { app, err := core.SystemRetrieveApplication(clientID) if err != nil { return nil, errReadingApplicationRecord } if app == nil { return nil, errApplicationNotFound } if clientSecret != app.ClientSecret { return nil, errInvalidClientSecret } return app, nil }
func validateScopes(core *roll.Core, r *http.Request) (bool, error) { scope := r.FormValue(oauth2Scope) log.Info("validating scope", scope) if scope == "" { return true, nil } scopeParts := strings.Fields(scope) if len(scopeParts) > 1 || scopeParts[0] != adminScope { log.Info("scope not allowed") return false, nil } subject := r.FormValue("username") validAdmin, err := core.IsAdmin(subject) if err != nil { return false, err } return validAdmin, nil }
func handleDeveloperPut(core *roll.Core, w http.ResponseWriter, r *http.Request) { var dev roll.Developer if err := parseRequest(r, &dev); err != nil { respondError(w, http.StatusBadRequest, err) return } if err := dev.Validate(); err != nil { respondError(w, http.StatusBadRequest, err) return } log.Printf("Handling put with payload %v", dev) email := strings.TrimPrefix(r.RequestURI, DevelopersURI) //If the user included the email inf the body we ignore it. Ignoring it lets us reuse the //developer struct for parsing the request, instead of having a projection of the developer //structure used to parse the input dev.Email = email //Extract the subject from the request header based on security mode subject, _, err := subjectAndAdminScopeFromRequestCtx(r) if err != nil { log.Print("Error extracting subject:", err.Error()) respondError(w, http.StatusInternalServerError, nil) return } //Set the developer id to the subject dev.ID = subject //Store the developer information if err := core.StoreDeveloper(&dev); err != nil { respondError(w, http.StatusInternalServerError, err) return } respondOk(w, nil) }
func validateInputParams(core *roll.Core, r *http.Request) (*roll.Application, error) { responseType := r.FormValue("response_type") if responseType != "token" && responseType != "code" { return nil, errors.New("response_type must be code or token") } //Client id is application key clientID := r.FormValue("client_id") app, err := core.SystemRetrieveApplication(clientID) if err != nil { return nil, err } if app == nil { return nil, errors.New("Invalid client id") } redirectURI := r.FormValue("redirect_uri") if app.RedirectURI != redirectURI { return nil, errors.New("redirect_uri does not match registered redirect URIs") } return app, nil }
func handleApplicationPost(core *roll.Core, w http.ResponseWriter, r *http.Request) { var app roll.Application if err := parseRequest(r, &app); err != nil { respondError(w, http.StatusBadRequest, err) return } //Assign a client ID id, err := core.GenerateID() if err != nil { respondError(w, http.StatusInternalServerError, err) return } app.ClientID = id //Validate the content if err := app.Validate(); err != nil { respondError(w, http.StatusBadRequest, err) return } //Extract the subject from the request header based on security mode subject, _, err := subjectAndAdminScopeFromRequestCtx(r) if err != nil { log.Print("Error extracting subject:", err.Error()) respondError(w, http.StatusInternalServerError, nil) return } app.DeveloperID = subject //Store the application definition log.Info("storing app def: ", app) err = core.CreateApplication(&app) if err != nil { log.Info("Error storing app def: ", err.Error()) switch err.(type) { case *repos.DuplicateAppdefError: respondError(w, http.StatusConflict, err) default: respondError(w, http.StatusInternalServerError, err) } return } //Generate a private/public key pair log.Info("Generate key pair") private, public, err := secrets.GenerateKeyPair() if err != nil { respondError(w, http.StatusBadRequest, err) return } //Store keys in secrets vault log.Info("store key pair in vault") err = core.StoreKeysForApp(id, private, public) if err != nil { respondError(w, http.StatusInternalServerError, err) return } //Return the client id log.Info("return client id: ", id) clientID := ApplicationCreatedResponse{ClientID: id} respondOk(w, clientID) }
func handleApplicationPut(core *roll.Core, w http.ResponseWriter, r *http.Request) { var app roll.Application if err := parseRequest(r, &app); err != nil { respondError(w, http.StatusBadRequest, err) return } //Make sure we use the clientID in the resource not any clientID sent in the JSON. clientID := strings.TrimPrefix(r.RequestURI, ApplicationsURI) if clientID == "" { respondError(w, http.StatusBadRequest, nil) return } app.ClientID = clientID //Validate the content if err := app.Validate(); err != nil { respondError(w, http.StatusBadRequest, err) return } //Extract the subject from the request header based on security mode subject, adminScope, err := subjectAndAdminScopeFromRequestCtx(r) if err != nil { log.Print("Error extracting subject:", err.Error()) respondError(w, http.StatusInternalServerError, nil) return } //Retrieve the app definition to update storedApp, err := core.RetrieveApplication(clientID, subject, adminScope) if err != nil { respondError(w, http.StatusInternalServerError, err) return } if storedApp == nil { respondError(w, http.StatusNotFound, nil) return } //Copy over the potential updates storedApp.ApplicationName = app.ApplicationName storedApp.DeveloperEmail = app.DeveloperEmail storedApp.LoginProvider = app.LoginProvider storedApp.RedirectURI = app.RedirectURI storedApp.DeveloperID = app.DeveloperID //Store the application definition log.Info("updating app def: ", app) err = core.UpdateApplication(&app, subject) if err != nil { log.Info("Error updating definition: ", err.Error()) switch err.(type) { case roll.NonOwnerUpdateError: respondError(w, http.StatusUnauthorized, err) case roll.NoSuchApplicationError: respondError(w, http.StatusNotFound, err) default: respondError(w, http.StatusInternalServerError, err) } } respondOk(w, nil) }
func handleCertPut(core *roll.Core, w http.ResponseWriter, r *http.Request) { //Extract client id clientID := strings.TrimPrefix(r.RequestURI, JWTFlowCertsURI) if clientID == "" { respondError(w, http.StatusNotFound, errors.New("Resource not specified")) return } log.Info("Putting cert for client_id: ", clientID) //Extract the subject from the request header based on security mode subject, _, err := subjectAndAdminScopeFromRequestCtx(r) if err != nil { log.Print("Error extracting subject: ", err.Error()) respondError(w, http.StatusInternalServerError, nil) return } //Parse body var certCtx CertPutCtx if err := parseRequest(r, &certCtx); err != nil { log.Info("Error parsing request body: ", err.Error()) respondError(w, http.StatusBadRequest, err) return } //Check body content log.Info("Checking content") err = checkBodyContent(certCtx) if err != nil { log.Info("Problem with content: ", err.Error()) respondError(w, http.StatusBadRequest, err) return } //Validate client secret log.Info("validating client secret") app, err := validateClientSecret(core, r, clientID, certCtx.ClientSecret) if err != nil { switch err { case errApplicationNotFound: respondNotFound(w) case errInvalidClientSecret: respondError(w, http.StatusUnauthorized, nil) default: respondError(w, http.StatusInternalServerError, err) } return } //Extract public key from cert log.Info("Extract public key") publicKeyPEM, err := extractPublicKeyFromCert(certCtx.CertPEM) if err != nil { respondError(w, http.StatusBadRequest, err) return } //Update the app with the public key. Note here we are adding the cert to the retrieved application //attributes. log.Info("Update app with signing key, etc") app.JWTFlowPublicKey = publicKeyPEM app.JWTFlowIssuer = certCtx.CertIssuer app.JWTFlowAudience = certCtx.CertAudience err = core.UpdateApplication(app, subject) if err != nil { switch err.(type) { case roll.NonOwnerUpdateError: respondError(w, http.StatusUnauthorized, err) case roll.NoSuchApplicationError: respondError(w, http.StatusNotFound, err) case roll.MissingJWTFlowIssuer: respondError(w, http.StatusBadRequest, err) case roll.MissingJWTFlowAudience: respondError(w, http.StatusBadRequest, err) default: respondError(w, http.StatusInternalServerError, err) } return } respondOk(w, nil) }