func (s *AuthSuite) TestRemoveUserRevokesAccessInGandalf(c *gocheck.C) { h := testHandler{} ts := s.startGandalfTestServer(&h) defer ts.Close() u := auth.User{Email: "*****@*****.**"} err := u.Create() c.Assert(err, gocheck.IsNil) defer s.conn.Users().Remove(bson.M{"email": u.Email}) t := auth.Team{Name: "painofsalvation", Users: []string{u.Email, s.user.Email}} err = s.conn.Teams().Insert(t) c.Assert(err, gocheck.IsNil) defer s.conn.Teams().Remove(bson.M{"_id": t.Name}) a := struct { Name string Teams []string }{Name: "myApp", Teams: []string{t.Name}} err = s.conn.Apps().Insert(a) c.Assert(err, gocheck.IsNil) defer s.conn.Apps().Remove(bson.M{"name": a.Name}) request, err := http.NewRequest("DELETE", "/users", nil) c.Assert(err, gocheck.IsNil) recorder := httptest.NewRecorder() err = RemoveUser(recorder, request, &u) c.Assert(err, gocheck.IsNil) c.Assert(h.url[0], gocheck.Equals, "/repository/revoke") c.Assert(h.method[0], gocheck.Equals, "DELETE") expected := `{"repositories":["myApp"],"users":["*****@*****.**"]}` c.Assert(string(h.body[0]), gocheck.Equals, expected) }
func (s *AuthSuite) TestRemoveUserWithTheUserBeingLastMemberOfATeam(c *gocheck.C) { h := testHandler{} ts := s.startGandalfTestServer(&h) defer ts.Close() u := auth.User{Email: "*****@*****.**"} err := u.Create() c.Assert(err, gocheck.IsNil) defer s.conn.Users().Remove(bson.M{"email": u.Email}) t := auth.Team{Name: "painofsalvation", Users: []string{u.Email}} err = s.conn.Teams().Insert(t) c.Assert(err, gocheck.IsNil) defer s.conn.Teams().Remove(bson.M{"_id": t.Name}) request, err := http.NewRequest("DELETE", "/users", nil) c.Assert(err, gocheck.IsNil) recorder := httptest.NewRecorder() err = RemoveUser(recorder, request, &u) c.Assert(err, gocheck.NotNil) e, ok := err.(*errors.Http) c.Assert(ok, gocheck.Equals, true) c.Assert(e.Code, gocheck.Equals, http.StatusForbidden) expected := `This user is the last member of the team "painofsalvation", so it cannot be removed. Please remove the team, them remove the user.` c.Assert(e.Message, gocheck.Equals, expected) }
func ServiceInfoHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error { serviceName := r.URL.Query().Get(":name") _, err := getServiceOrError(serviceName, u) if err != nil { return err } instances := []service.ServiceInstance{} teams, err := u.Teams() if err != nil { return err } conn, err := db.Conn() if err != nil { return err } defer conn.Close() teamsNames := auth.GetTeamsNames(teams) q := bson.M{"service_name": serviceName, "teams": bson.M{"$in": teamsNames}} err = conn.ServiceInstances().Find(q).All(&instances) if err != nil { return err } b, err := json.Marshal(instances) if err != nil { return nil } w.Write(b) return nil }
func CreateServiceInstance(name string, service *Service, plan *Plan, user *auth.User) error { err := validateServiceInstanceName(name) if err != nil { return err } instance := ServiceInstance{ Name: name, ServiceName: service.Name, } if plan != nil { instance.PlanName = plan.Name } teams, err := user.Teams() if err != nil { return err } instance.Teams = make([]string, 0, len(teams)) for _, team := range teams { if service.HasTeam(&team) || !service.IsRestricted { instance.Teams = append(instance.Teams, team.Name) } } actions := []*action.Action{&createServiceInstance, &insertServiceInstance} pipeline := action.NewPipeline(actions...) return pipeline.Execute(*service, instance) }
// CreateApp creates a new app. // // Creating a new app is a process composed of four steps: // // 1. Save the app in the database // 2. Create IAM credentials for the app // 3. Create S3 bucket for the app (if the bucket support is enabled) // 4. Create the git repository using gandalf // 5. Provision units within the provisioner func CreateApp(app *App, user *auth.User) error { teams, err := user.Teams() if err != nil { return err } if len(teams) == 0 { return NoTeamsError{} } if _, err := getPlatform(app.Platform); err != nil { return err } app.SetTeams(teams) app.Owner = user.Email if !app.isValid() { msg := "Invalid app name, your app should have at most 63 " + "characters, containing only lower case letters, numbers or dashes, " + "starting with a letter." return &errors.ValidationError{Message: msg} } actions := []*action.Action{&reserveUserApp, &createAppQuota, &insertApp} useS3, _ := config.GetBool("bucket-support") if useS3 { actions = append(actions, &createIAMUserAction, &createIAMAccessKeyAction, &createBucketAction, &createUserPolicyAction) } actions = append(actions, &exportEnvironmentsAction, &createRepository, &provisionApp) pipeline := action.NewPipeline(actions...) err = pipeline.Execute(app, user) if err != nil { return &AppCreationError{app: app.Name, Err: err} } return nil }
func removeUserFromTeamInGandalf(u *auth.User, team *auth.Team) error { gURL := repository.ServerURL() teamApps, err := team.AllowedApps() if err != nil { return err } userApps, err := u.AllowedApps() if err != nil { return err } appsToRemove := make([]string, 0, len(teamApps)) for _, teamApp := range teamApps { found := false for _, userApp := range userApps { if userApp == teamApp { found = true break } } if !found { appsToRemove = append(appsToRemove, teamApp) } } client := gandalf.Client{Endpoint: gURL} if err := client.RevokeAccess(appsToRemove, []string{u.Email}); err != nil { return fmt.Errorf("Failed to revoke access from git repositories: %s", err) } return nil }
func createUser(w http.ResponseWriter, r *http.Request) error { var u auth.User err := json.NewDecoder(r.Body).Decode(&u) if err != nil { return &errors.HTTP{Code: http.StatusBadRequest, Message: err.Error()} } if !validation.ValidateEmail(u.Email) { return &errors.HTTP{Code: http.StatusBadRequest, Message: emailError} } if !validation.ValidateLength(u.Password, passwordMinLen, passwordMaxLen) { return &errors.HTTP{Code: http.StatusBadRequest, Message: passwordError} } gURL := repository.ServerURL() c := gandalf.Client{Endpoint: gURL} if _, err := c.NewUser(u.Email, keyToMap(u.Keys)); err != nil { return fmt.Errorf("Failed to create user in the git server: %s", err) } if err := u.Create(); err == nil { rec.Log(u.Email, "create-user") if limit, err := config.GetUint("quota:apps-per-user"); err == nil { quota.Create(u.Email, uint(limit)) } w.WriteHeader(http.StatusCreated) return nil } if _, err = auth.GetUserByEmail(u.Email); err == nil { err = &errors.HTTP{Code: http.StatusConflict, Message: "This email is already registered"} } return err }
func ListTeams(w http.ResponseWriter, r *http.Request, u *auth.User) error { teams, err := u.Teams() if err != nil { return err } if len(teams) > 0 { var result []map[string]string for _, team := range teams { result = append(result, map[string]string{"name": team.Name}) } b, err := json.Marshal(result) if err != nil { return err } n, err := w.Write(b) if err != nil { return err } if n != len(b) { return &errors.Http{Code: http.StatusInternalServerError, Message: "Failed to write response body."} } } else { w.WriteHeader(http.StatusNoContent) } return nil }
func (s *AuthSuite) TestRemoveUserFromTeamShouldRemoveOnlyAppsInThatTeamInGandalfWhenUserIsInMoreThanOneTeam(c *gocheck.C) { h := testHandler{} ts := s.startGandalfTestServer(&h) defer ts.Close() u := auth.User{Email: "*****@*****.**", Password: "******"} err := u.Create() c.Assert(err, gocheck.IsNil) defer s.conn.Users().Remove(bson.M{"email": u.Email}) s.team.AddUser(&u) s.conn.Teams().UpdateId(s.team.Name, s.team) team2 := auth.Team{Name: "team2", Users: []string{u.Email}} err = s.conn.Teams().Insert(&team2) c.Assert(err, gocheck.IsNil) defer s.conn.Teams().RemoveId(team2.Name) app1 := app.App{Name: "app1", Teams: []string{s.team.Name}} err = s.conn.Apps().Insert(&app1) c.Assert(err, gocheck.IsNil) defer s.conn.Apps().Remove(bson.M{"name": app1.Name}) app2 := app.App{Name: "app2", Teams: []string{team2.Name}} err = s.conn.Apps().Insert(&app2) c.Assert(err, gocheck.IsNil) defer s.conn.Apps().Remove(bson.M{"name": app2.Name}) err = removeUserFromTeam(u.Email, s.team.Name, s.user) c.Assert(err, gocheck.IsNil) expected := `{"repositories":["app1"],"users":["*****@*****.**"]}` c.Assert(len(h.body), gocheck.Equals, 1) c.Assert(string(h.body[0]), gocheck.Equals, expected) s.conn.Teams().FindId(s.team.Name).One(s.team) c.Assert(s.team, gocheck.Not(ContainsUser), &u) // just in case }
func removeUserFromTeam(email, teamName string, u *auth.User) error { conn, err := db.Conn() if err != nil { return err } defer conn.Close() team := new(auth.Team) err = conn.Teams().FindId(teamName).One(team) if err != nil { return &errors.Http{Code: http.StatusNotFound, Message: "Team not found"} } if !team.ContainsUser(u) { msg := fmt.Sprintf("You are not authorized to remove a member from the team %s", team.Name) return &errors.Http{Code: http.StatusUnauthorized, Message: msg} } if len(team.Users) == 1 { msg := "You can not remove this user from this team, because it is the last user within the team, and a team can not be orphaned" return &errors.Http{Code: http.StatusForbidden, Message: msg} } user := auth.User{Email: email} err = user.Get() if err != nil { return &errors.Http{Code: http.StatusNotFound, Message: err.Error()} } err = removeUserFromTeamInGandalf(&user, team.Name) if err != nil { return nil } return removeUserFromTeamInDatabase(&user, team) }
func CreateUser(w http.ResponseWriter, r *http.Request) error { var u auth.User err := json.NewDecoder(r.Body).Decode(&u) if err != nil { return &errors.Http{Code: http.StatusBadRequest, Message: err.Error()} } if !validation.ValidateEmail(u.Email) { return &errors.Http{Code: http.StatusPreconditionFailed, Message: emailError} } if !validation.ValidateLength(u.Password, passwordMinLen, passwordMaxLen) { return &errors.Http{Code: http.StatusPreconditionFailed, Message: passwordError} } gUrl := repository.GitServerUri() c := gandalf.Client{Endpoint: gUrl} if _, err := c.NewUser(u.Email, keyToMap(u.Keys)); err != nil { return fmt.Errorf("Failed to create user in the git server: %s", err) } if err := u.Create(); err == nil { w.WriteHeader(http.StatusCreated) return nil } if u.Get() == nil { err = &errors.Http{Code: http.StatusConflict, Message: "This email is already registered"} } return err }
func removeKeyFromDatabase(key *auth.Key, u *auth.User) error { conn, err := db.Conn() if err != nil { return err } defer conn.Close() u.RemoveKey(*key) return conn.Users().Update(bson.M{"email": u.Email}, u) }
func getTeamNames(u *auth.User) ([]string, error) { var ( teams []auth.Team err error ) if teams, err = u.Teams(); err != nil { return nil, err } return auth.GetTeamsNames(teams), nil }
func addUserToTeamInGandalf(email string, u *auth.User, t *auth.Team) error { gURL := repository.ServerURL() alwdApps, err := u.AllowedApps() if err != nil { return fmt.Errorf("Failed to obtain allowed apps to grant: %s", err.Error()) } if err := (&gandalf.Client{Endpoint: gURL}).GrantAccess(alwdApps, []string{email}); err != nil { return fmt.Errorf("Failed to grant access to git repositories: %s", err) } return nil }
func removeUserFromTeamInGandalf(u *auth.User, team string) error { gURL := repository.ServerURL() alwdApps, err := u.AllowedAppsByTeam(team) if err != nil { return err } if err := (&gandalf.Client{Endpoint: gURL}).RevokeAccess(alwdApps, []string{u.Email}); err != nil { return fmt.Errorf("Failed to revoke access from git repositories: %s", err) } return nil }
// revomeKeyFromUser removes a key from the given user's document // // Also removes the key from gandalf. // // This functions makes uses of git:host, git:protocol and optionaly git:port configurations func removeKeyFromUser(content string, u *auth.User) error { key, index := u.FindKey(auth.Key{Content: content}) if index < 0 { return &errors.Http{Code: http.StatusNotFound, Message: "User does not have this key"} } err := removeKeyFromGandalf(&key, u) if err != nil { return err } return removeKeyFromDatabase(&key, u) }
func (s *AuthSuite) TestListTeamsReturns204IfTheUserHasNoTeam(c *gocheck.C) { u := auth.User{Email: "*****@*****.**", Password: "******"} err := u.Create() c.Assert(err, gocheck.IsNil) defer s.conn.Users().Remove(bson.M{"email": u.Email}) request, err := http.NewRequest("GET", "/teams", nil) c.Assert(err, gocheck.IsNil) recorder := httptest.NewRecorder() err = ListTeams(recorder, request, &u) c.Assert(err, gocheck.IsNil) c.Assert(recorder.Code, gocheck.Equals, http.StatusNoContent) }
func getApp(name string, u *auth.User) (app.App, error) { a, err := app.GetByName(name) if err != nil { return app.App{}, &errors.HTTP{Code: http.StatusNotFound, Message: fmt.Sprintf("App %s not found.", name)} } if u.IsAdmin() { return *a, nil } if !auth.CheckUserAccess(a.Teams, u) { return *a, &errors.HTTP{Code: http.StatusForbidden, Message: "User does not have access to this app"} } return *a, nil }
// CreateApp creates a new app. // // Creating a new app is a process composed of four steps: // // 1. Save the app in the database // 2. Create IAM credentials for the app // 3. Create S3 bucket for the app (if the bucket support is enabled) // 4. Create the git repository using gandalf // 5. Provision units within the provisioner func CreateApp(app *App, user *auth.User) error { teams, err := user.Teams() if err != nil { return err } if len(teams) == 0 { return NoTeamsError{} } if _, err := getPlatform(app.Platform); err != nil { return err } // app.Memory is empty, no custom memory passed from CLI if app.Memory < 1 { // get default memory limit from tsuru config configMemory, err := config.GetInt("docker:memory") if err != nil { // no default memory set in config (or error when reading), set it as unlimited (0) app.Memory = 0 } else { // default memory set in config, use that. app.Memory = configMemory } } if err := app.setTeamOwner(teams); err != nil { return err } app.SetTeams(teams) app.Owner = user.Email if !app.isValid() { msg := "Invalid app name, your app should have at most 63 " + "characters, containing only lower case letters, numbers or dashes, " + "starting with a letter." return &errors.ValidationError{Message: msg} } actions := []*action.Action{&reserveUserApp, &insertApp} useS3, _ := config.GetBool("bucket-support") if useS3 { actions = append(actions, &createIAMUserAction, &createIAMAccessKeyAction, &createBucketAction, &createUserPolicyAction) } actions = append(actions, &exportEnvironmentsAction, &createRepository, &provisionApp) pipeline := action.NewPipeline(actions...) err = pipeline.Execute(app, user) if err != nil { return &AppCreationError{app: app.Name, Err: err} } return nil }
func CreateInstanceHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error { log.Print("Receiving request to create a service instance") b, err := ioutil.ReadAll(r.Body) if err != nil { log.Print("Got error while reading request body:") log.Print(err.Error()) return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()} } var sJson map[string]string err = json.Unmarshal(b, &sJson) if err != nil { log.Print("Got a problem while unmarshalling request's json:") log.Print(err.Error()) return &errors.Http{Code: http.StatusInternalServerError, Message: err.Error()} } var s service.Service err = validateInstanceForCreation(&s, sJson, u) if err != nil { log.Print("Got error while validation:") log.Print(err.Error()) return err } var teamNames []string teams, err := u.Teams() if err != nil { return err } for _, t := range teams { if s.HasTeam(&t) || !s.IsRestricted { teamNames = append(teamNames, t.Name) } } si := service.ServiceInstance{ Name: sJson["name"], ServiceName: sJson["service_name"], Teams: teamNames, } if err = s.ProductionEndpoint().Create(&si); err != nil { log.Print("Error while calling create action from service api.") log.Print(err.Error()) return err } err = si.Create() if err != nil { return err } fmt.Fprint(w, "success") return nil }
func (s *AuthSuite) TestLoginShouldreturnErrorIfThePasswordDoesNotMatch(c *gocheck.C) { u := auth.User{Email: "*****@*****.**", Password: "******"} u.Create() b := bytes.NewBufferString(`{"password":"******"}`) request, err := http.NewRequest("POST", "/users/[email protected]/tokens?:[email protected]", b) c.Assert(err, gocheck.IsNil) request.Header.Set("Content-type", "application/json") recorder := httptest.NewRecorder() err = Login(recorder, request) c.Assert(err, gocheck.NotNil) c.Assert(err, gocheck.ErrorMatches, "^Authentication failed, wrong password$") e, ok := err.(*errors.Http) c.Assert(ok, gocheck.Equals, true) c.Assert(e.Code, gocheck.Equals, http.StatusUnauthorized) }
func (s *AuthSuite) TestCreateUserHandlerSavesTheUserInTheDatabase(c *gocheck.C) { h := testHandler{} ts := s.startGandalfTestServer(&h) defer ts.Close() b := bytes.NewBufferString(`{"email":"*****@*****.**","password":"******"}`) request, err := http.NewRequest("POST", "/users", b) c.Assert(err, gocheck.IsNil) request.Header.Set("Content-type", "application/json") recorder := httptest.NewRecorder() err = CreateUser(recorder, request) c.Assert(err, gocheck.IsNil) u := auth.User{Email: "*****@*****.**"} err = u.Get() c.Assert(err, gocheck.IsNil) }
func GetServicesByOwnerTeams(teamKind string, u *auth.User) ([]Service, error) { teams, err := u.Teams() if err != nil { return nil, err } conn, err := db.Conn() if err != nil { return nil, err } defer conn.Close() teamsNames := auth.GetTeamsNames(teams) q := bson.M{teamKind: bson.M{"$in": teamsNames}} var services []Service err = conn.Services().Find(q).All(&services) return services, err }
// addKeyToUser adds a key to a user in mongodb and send the key to the git server // in order to allow ssh-ing into git server. func addKeyToUser(content string, u *auth.User) error { key := auth.Key{Content: content} if u.HasKey(key) { return &errors.Http{Code: http.StatusConflict, Message: "User already has this key"} } actions := []*action.Action{ &addKeyInGandalfAction, &addKeyInDatabaseAction, } pipeline := action.NewPipeline(actions...) err := pipeline.Execute(&key, u) if err != nil { return err } return nil }
func CreateAppHandler(w http.ResponseWriter, r *http.Request, u *auth.User) error { var a app.App var japp jsonApp defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) if err != nil { return err } if err = json.Unmarshal(body, &japp); err != nil { return err } a.Name = japp.Name a.Framework = japp.Framework if japp.Units == 0 { japp.Units = 1 } teams, err := u.Teams() if err != nil { return err } if len(teams) < 1 { msg := "In order to create an app, you should be member of at least one team" return &errors.Http{Code: http.StatusForbidden, Message: msg} } err = app.CreateApp(&a, japp.Units, teams) if err != nil { log.Printf("Got error while creating app: %s", err) if e, ok := err.(*errors.ValidationError); ok { return &errors.Http{Code: http.StatusPreconditionFailed, Message: e.Message} } if strings.Contains(err.Error(), "key error") { msg := fmt.Sprintf(`There is already an app named "%s".`, a.Name) return &errors.Http{Code: http.StatusConflict, Message: msg} } return err } msg := map[string]string{ "status": "success", "repository_url": repository.GetUrl(a.Name), } jsonMsg, err := json.Marshal(msg) if err != nil { return err } fmt.Fprintf(w, "%s", jsonMsg) return nil }
func (s *AuthSuite) TestRemoveUser(c *gocheck.C) { h := testHandler{} ts := s.startGandalfTestServer(&h) defer ts.Close() u := auth.User{Email: "*****@*****.**"} err := u.Create() c.Assert(err, gocheck.IsNil) defer s.conn.Users().Remove(bson.M{"email": u.Email}) request, err := http.NewRequest("DELETE", "/users", nil) c.Assert(err, gocheck.IsNil) recorder := httptest.NewRecorder() err = RemoveUser(recorder, request, &u) c.Assert(err, gocheck.IsNil) n, err := s.conn.Users().Find(bson.M{"email": u.Email}).Count() c.Assert(err, gocheck.IsNil) c.Assert(n, gocheck.Equals, 0) }
func (s *S) TestReserveUserAppForwardQuotaExceeded(c *gocheck.C) { user := auth.User{ Email: "*****@*****.**", Quota: quota.Quota{Limit: 1, InUse: 1}, } err := user.Create() c.Assert(err, gocheck.IsNil) defer s.conn.Users().Remove(bson.M{"email": user.Email}) app := App{ Name: "clap", Platform: "django", } previous, err := reserveUserApp.Forward(action.FWContext{Params: []interface{}{app, user}}) c.Assert(previous, gocheck.IsNil) _, ok := err.(*quota.QuotaExceededError) c.Assert(ok, gocheck.Equals, true) }
func Login(w http.ResponseWriter, r *http.Request) error { var pass map[string]string err := json.NewDecoder(r.Body).Decode(&pass) if err != nil { return &errors.Http{Code: http.StatusBadRequest, Message: "Invalid JSON"} } password, ok := pass["password"] if !ok { msg := "You must provide a password to login" return &errors.Http{Code: http.StatusBadRequest, Message: msg} } if !validation.ValidateLength(password, passwordMinLen, passwordMaxLen) { return &errors.Http{Code: http.StatusPreconditionFailed, Message: passwordError} } u := auth.User{Email: r.URL.Query().Get(":email")} if !validation.ValidateEmail(u.Email) { return &errors.Http{Code: http.StatusPreconditionFailed, Message: emailError} } err = u.Get() if err != nil { return &errors.Http{Code: http.StatusNotFound, Message: "User not found"} } if u.Login(password) { t, _ := u.CreateToken() fmt.Fprintf(w, `{"token":"%s"}`, t.Token) return nil } msg := "Authentication failed, wrong password" return &errors.Http{Code: http.StatusUnauthorized, Message: msg} }
// ChangePassword changes the password from the logged in user. // // It reads the request body in JSON format. The JSON in the request body // should contain two attributes: // // - old: the old password // - new: the new password // // This handler will return 403 if the password didn't match the user, or 412 // if the new password is invalid. func ChangePassword(w http.ResponseWriter, r *http.Request, u *auth.User) error { var body map[string]string err := json.NewDecoder(r.Body).Decode(&body) if err != nil { return &errors.Http{ Code: http.StatusBadRequest, Message: "Invalid JSON.", } } if body["old"] == "" || body["new"] == "" { return &errors.Http{ Code: http.StatusBadRequest, Message: "Both the old and the new passwords are required.", } } if !u.Login(body["old"]) { return &errors.Http{ Code: http.StatusForbidden, Message: "The given password didn't match the user's current password.", } } if !validation.ValidateLength(body["new"], passwordMinLen, passwordMaxLen) { return &errors.Http{ Code: http.StatusPreconditionFailed, Message: passwordError, } } u.Password = body["new"] u.HashPassword() return u.Update() }
func GetServiceInstancesByServicesAndTeams(services []Service, u *auth.User) ([]ServiceInstance, error) { var instances []ServiceInstance teams, err := u.Teams() if err != nil { return nil, err } if len(teams) == 0 { return nil, nil } conn, err := db.Conn() if err != nil { return nil, err } defer conn.Close() q, f := genericServiceInstancesFilter(services, auth.GetTeamsNames(teams)) err = conn.ServiceInstances().Find(q).Select(f).All(&instances) return instances, err }