func TestAddAndRetrieveApp(t *testing.T) { app := new(roll.Application) app.ApplicationName = "an app" app.ClientID = "123" app.ClientSecret = "hush" app.DeveloperEmail = "*****@*****.**" app.DeveloperID = "foo" app.LoginProvider = "auth0" app.RedirectURI = "neither here nor there" appRepo := NewMBDAppRepo() err := appRepo.CreateApplication(app) if assert.Nil(t, err) { defer appRepo.delete(app) } retapp, err := appRepo.RetrieveAppByNameAndDevEmail("an app", "*****@*****.**") assert.Nil(t, err) if assert.NotNil(t, app) { assert.Equal(t, app.ApplicationName, retapp.ApplicationName) assert.Equal(t, app.ClientID, retapp.ClientID) assert.Equal(t, app.ClientSecret, retapp.ClientSecret) assert.Equal(t, app.DeveloperEmail, retapp.DeveloperEmail) assert.Equal(t, app.DeveloperID, retapp.DeveloperID) assert.Equal(t, app.LoginProvider, retapp.LoginProvider) assert.Equal(t, app.RedirectURI, retapp.RedirectURI) } retapp, err = appRepo.RetrieveApplication(app.ClientID, app.DeveloperID, false) assert.Nil(t, err) if assert.NotNil(t, app) { assert.Equal(t, app.ApplicationName, retapp.ApplicationName) assert.Equal(t, app.ClientID, retapp.ClientID) assert.Equal(t, app.ClientSecret, retapp.ClientSecret) assert.Equal(t, app.DeveloperEmail, retapp.DeveloperEmail) assert.Equal(t, app.DeveloperID, retapp.DeveloperID) assert.Equal(t, app.LoginProvider, retapp.LoginProvider) assert.Equal(t, app.RedirectURI, retapp.RedirectURI) } retapp, err = appRepo.RetrieveApplication(app.ClientID, "huh", true) assert.Nil(t, err) if assert.NotNil(t, app) { assert.Equal(t, app.ApplicationName, retapp.ApplicationName) assert.Equal(t, app.ClientID, retapp.ClientID) assert.Equal(t, app.ClientSecret, retapp.ClientSecret) assert.Equal(t, app.DeveloperEmail, retapp.DeveloperEmail) assert.Equal(t, app.DeveloperID, retapp.DeveloperID) assert.Equal(t, app.LoginProvider, retapp.LoginProvider) assert.Equal(t, app.RedirectURI, retapp.RedirectURI) } retapp, err = appRepo.SystemRetrieveApplication(app.ClientID) assert.Nil(t, err) assert.Equal(t, app.ClientID, retapp.ClientID) retapp, err = appRepo.RetrieveApplication(app.ClientID, "huh", false) assert.NotNil(t, err) assert.Nil(t, retapp) }
func (ar *MariaDBAppRepo) CreateApplication(app *roll.Application) error { //Generate a client secret as needed if app.ClientSecret == "" { clientSecret, err := secrets.GenerateClientSecret() if err != nil { return err } app.ClientSecret = clientSecret } //Check JWT flow parts are ok if err := repos.CheckJWTCertParts(app); err != nil { return err } //Insert the app const appSql = `insert into rolldb.application(applicationName, clientId, clientSecret, developerEmail, developerId, loginProvider, redirectUri,jwtFlowAudience, jwtFlowIssuer, jwtFlowPublicKey) values(?,?,?,?,?,?,?,?,?,?) ` stmt, err := ar.db.Prepare(appSql) if err != nil { return err } defer stmt.Close() _, err = stmt.Exec( app.ApplicationName, app.ClientID, app.ClientSecret, app.DeveloperEmail, app.DeveloperID, app.LoginProvider, app.RedirectURI, app.JWTFlowAudience, app.JWTFlowIssuer, app.JWTFlowPublicKey, ) if err != nil { log.Info(err) sqlErr := err.(*mysql.MySQLError) switch sqlErr.Number { case 1062: log.Info("Duplicate app definition found") return repos.NewDuplicationAppdefError(app.ApplicationName, app.DeveloperEmail) default: return err } } return nil }
func TestUpdateNoSuchApp(t *testing.T) { appRepo := NewMBDAppRepo() //Specify an app app := new(roll.Application) app.ApplicationName = "an app" app.ClientID = "123" app.DeveloperEmail = "*****@*****.**" app.DeveloperID = "foo" app.LoginProvider = "auth0" app.RedirectURI = "neither here nor there" err := appRepo.UpdateApplication(app, app.DeveloperID) assert.NotNil(t, err) }
func TestDuplicateAppCreateGeneratesError(t *testing.T) { app := new(roll.Application) app.ApplicationName = "an app" app.ClientID = "123" app.DeveloperEmail = "*****@*****.**" app.DeveloperID = "foo" app.LoginProvider = "auth0" app.RedirectURI = "neither here nor there" appRepo := NewMBDAppRepo() err := appRepo.CreateApplication(app) if assert.Nil(t, err) { defer appRepo.delete(app) } err = appRepo.CreateApplication(app) assert.NotNil(t, err) }
func TestSecretGeneratedWhenNeede(t *testing.T) { app := new(roll.Application) app.ApplicationName = "an app" app.ClientID = "123" app.DeveloperEmail = "*****@*****.**" app.DeveloperID = "foo" app.LoginProvider = "auth0" app.RedirectURI = "neither here nor there" appRepo := NewMBDAppRepo() err := appRepo.CreateApplication(app) if assert.Nil(t, err) { defer appRepo.delete(app) } retapp, err := appRepo.RetrieveAppByNameAndDevEmail("an app", "*****@*****.**") assert.Nil(t, err) assert.NotEqual(t, "", retapp.ClientSecret) }
//CreateApplication stores an application definition in DynamoDB func (dar *DynamoAppRepo) CreateApplication(app *roll.Application) error { log.Info("create application") //Make sure we are not creating a new application definition for an existing //application name/developer email combination existing, err := dar.RetrieveAppByNameAndDevEmail(app.ApplicationName, app.DeveloperEmail) if err != nil { log.Info("Internal error attempting to check for duplicate app: ", err.Error()) return err } if existing != nil { log.Info("Duplicate app definition found") return NewDuplicationAppdefError(app.ApplicationName, app.DeveloperEmail) } if app.ClientSecret == "" { clientSecret, err := secrets.GenerateClientSecret() if err != nil { return err } app.ClientSecret = clientSecret } appAttrs := map[string]*dynamodb.AttributeValue{ ClientID: {S: aws.String(app.ClientID)}, ApplicationName: {S: aws.String(app.ApplicationName)}, ClientSecret: {S: aws.String(app.ClientSecret)}, DeveloperEmail: {S: aws.String(app.DeveloperEmail)}, DeveloperID: {S: aws.String(app.DeveloperID)}, RedirectUri: {S: aws.String(app.RedirectURI)}, LoginProvider: {S: aws.String(app.LoginProvider)}, } if err := CheckJWTCertParts(app); err != nil { return err } if app.JWTFlowPublicKey != "" { appAttrs[JWTFlowPublicKey] = &dynamodb.AttributeValue{ S: aws.String(app.JWTFlowPublicKey), } appAttrs[JWTFlowIssuer] = &dynamodb.AttributeValue{ S: aws.String(app.JWTFlowIssuer), } appAttrs[JWTFlowAudience] = &dynamodb.AttributeValue{ S: aws.String(app.JWTFlowAudience), } } params := &dynamodb.PutItemInput{ TableName: aws.String("Application"), ConditionExpression: aws.String("attribute_not_exists(ClientID)"), Item: appAttrs, } _, err = dar.client.PutItem(params) return err }
func init() { var dev roll.Developer var app roll.Application var retrievedApp roll.Application var clientId string var reRegisterStatus int var duplicationErrorMessage string Before("@apptests", func() { testutils.URLGuard("http://localhost:3000/v1/developers") }) Given(`^a developer registered with the portal$`, func() { dev = testutils.CreateNewTestDev() resp := rollhttp.TestHTTPPutWithRollSubject(T, "http://localhost:3000/v1/developers/"+dev.Email, dev) println("resp is", resp) assert.Equal(T, http.StatusNoContent, resp.StatusCode) }) And(`^they have a new application they wish to register$`, func() { app = roll.Application{ ApplicationName: "int test app name", DeveloperEmail: dev.Email, RedirectURI: "http://localhost:3000/ab", LoginProvider: "xtrac://localhost:9000", } }) Then(`^the application should be successfully registered$`, func() { resp := rollhttp.TestHTTPPostWithRollSubject(T, "http://localhost:3000/v1/applications", app) assert.Equal(T, http.StatusOK, resp.StatusCode) var appCreatedResponse rollhttp.ApplicationCreatedResponse dec := json.NewDecoder(resp.Body) err := dec.Decode(&appCreatedResponse) assert.Nil(T, err) assert.True(T, len(appCreatedResponse.ClientID) > 0) clientId = appCreatedResponse.ClientID }) Given(`^a registered application$`, func() { retrieveAppDefinition(clientId, &retrievedApp) }) Then(`^the details associated with the application can be retrieved$`, func() { assert.Equal(T, app.ApplicationName, retrievedApp.ApplicationName) assert.Equal(T, app.DeveloperEmail, retrievedApp.DeveloperEmail) assert.Equal(T, app.RedirectURI, retrievedApp.RedirectURI) assert.Equal(T, app.LoginProvider, retrievedApp.LoginProvider) assert.Equal(T, clientId, retrievedApp.ClientID) assert.True(T, len(retrievedApp.ClientSecret) > 0) assert.Equal(T, retrievedApp.JWTFlowPublicKey, "") }) Given(`^an application has already been registered$`, func() { assert.True(T, len(clientId) > 0) }) And(`^a developer attempts to register an application with the same name$`, func() { resp := rollhttp.TestHTTPPostWithRollSubject(T, "http://localhost:3000/v1/applications", app) reRegisterStatus = resp.StatusCode defer resp.Body.Close() bodyBytes, err := ioutil.ReadAll(resp.Body) assert.Nil(T, err) duplicationErrorMessage = string(bodyBytes) }) Then(`^an error is returned with status code StatusConflict$`, func() { assert.Equal(T, http.StatusConflict, reRegisterStatus) }) And(`^the error message indicates a duplicate registration was attempted$`, func() { assert.True(T, strings.Contains(duplicationErrorMessage, "definition exists for application")) }) Given(`^a registered application to update$`, func() { assert.True(T, len(clientId) > 0) }) And(`^there are updates to make to the application defnition$`, func() { app.RedirectURI = "http://localhost:3000/son_of_callback" }) Then(`^the application can be updated$`, func() { resp := rollhttp.TestHTTPPutWithRollSubject(T, "http://localhost:3000/v1/applications/"+clientId, app) assert.Equal(T, http.StatusNoContent, resp.StatusCode) }) And(`^the updates are reflected when retrieving the application definition anew$`, func() { retrieveAppDefinition(clientId, &retrievedApp) assert.Equal(T, "http://localhost:3000/son_of_callback", retrievedApp.RedirectURI) }) }
func TestUpdateApp(t *testing.T) { appRepo := NewMBDAppRepo() //Count the apps prior to creating one apps, err := appRepo.ListApplications("foo", true) assert.Nil(t, err) adminCount := len(apps) //No apps see with a user id of not foo and not an admin apps, err = appRepo.ListApplications("not foo", false) assert.Nil(t, err) assert.Equal(t, 0, len(apps)) //Create an app app := new(roll.Application) app.ApplicationName = "an app" app.ClientID = "123" app.DeveloperEmail = "*****@*****.**" app.DeveloperID = "foo" app.LoginProvider = "auth0" app.RedirectURI = "neither here nor there" err = appRepo.CreateApplication(app) if assert.Nil(t, err) { defer appRepo.delete(app) } err = appRepo.UpdateApplication(app, "no way jose") assert.NotNil(t, err) err = appRepo.UpdateApplication(app, app.DeveloperID) assert.Nil(t, err) app.JWTFlowAudience = "aud" app.JWTFlowIssuer = "iss" app.JWTFlowPublicKey = "key to the city" appRepo.UpdateApplication(app, app.DeveloperID) retapp, err := appRepo.SystemRetrieveApplicationByJWTFlowAudience("aud") assert.Nil(t, err) if assert.NotNil(t, app) { assert.Equal(t, app.ApplicationName, retapp.ApplicationName) assert.Equal(t, app.ClientID, retapp.ClientID) assert.Equal(t, app.ClientSecret, retapp.ClientSecret) assert.Equal(t, app.DeveloperEmail, retapp.DeveloperEmail) assert.Equal(t, app.DeveloperID, retapp.DeveloperID) assert.Equal(t, app.LoginProvider, retapp.LoginProvider) assert.Equal(t, app.RedirectURI, retapp.RedirectURI) assert.Equal(t, app.JWTFlowAudience, retapp.JWTFlowAudience) assert.Equal(t, app.JWTFlowIssuer, retapp.JWTFlowIssuer) assert.Equal(t, app.JWTFlowPublicKey, retapp.JWTFlowPublicKey) } //Admin user should see an additional app in the list apps, err = appRepo.ListApplications("foo", true) assert.Nil(t, err) assert.Equal(t, adminCount+1, len(apps)) //User adding the app should see a list with 1 entry apps, err = appRepo.ListApplications("foo", false) assert.Nil(t, err) assert.Equal(t, 1, len(apps)) }
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) }