Beispiel #1
0
//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
}
Beispiel #2
0
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)
}
Beispiel #3
0
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)
}
Beispiel #4
0
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)
}
Beispiel #5
0
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
}
Beispiel #6
0
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
}
Beispiel #7
0
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
}
Beispiel #8
0
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
}
Beispiel #9
0
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
}
Beispiel #10
0
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)
}
Beispiel #11
0
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
}
Beispiel #12
0
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
}
Beispiel #13
0
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)
}
Beispiel #14
0
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
}
Beispiel #15
0
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)

}
Beispiel #16
0
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)
}
Beispiel #17
0
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)
}