// BuildStack fetches stack details from MongoDB. // // When nil error is returned, the b.Stack field is non-nil. func (b *Builder) BuildStack(stackID string, credentials map[string][]string) error { var overallErr error computeStack, err := modelhelper.GetComputeStack(stackID) if err != nil { return models.ResError(err, "jComputeStack") } b.Stack = &stack.Stack{ ID: computeStack.Id, Stack: computeStack, Machines: make([]string, len(computeStack.Machines)), Credentials: make(map[string][]string), } for i, m := range b.Stack.Stack.Machines { b.Stack.Machines[i] = m.Hex() } baseStackID := b.Stack.Stack.BaseStackId.Hex() // If fetching jStackTemplate fails, it might got deleted outside. // Continue building stack and let the caller decide, whether missing // jStackTemplate is fatal or not (e.g. for apply operations it's fatal, // for destroy ones - not). if stackTemplate, err := modelhelper.GetStackTemplate(baseStackID); err == nil { // first copy admin/group based credentials for k, v := range stackTemplate.Credentials { b.Stack.Credentials[k] = v } b.Stack.Template = stackTemplate.Template.Content } else { overallErr = models.ResError(err, "jStackTemplate") } // copy user based credentials for k, v := range computeStack.Credentials { // however don't override anything the admin already added if _, ok := b.Stack.Credentials[k]; !ok { b.Stack.Credentials[k] = v } } // Set or override credentials when passed in apply request. for k, v := range credentials { if len(v) != 0 { b.Stack.Credentials[k] = v } } b.Log.Debug("Stack built: len(machines)=%d, len(credentials)=%d, overallErr=%v", len(b.Stack.Machines), len(b.Stack.Credentials), overallErr) return overallErr }
// fetchOne returns all user's teams. func (m *MongoDatabase) fetchAll(user string) ([]*Team, error) { groups, err := m.adapter.FetchAccountGroups(user) if err != nil && err != mgo.ErrNotFound { return nil, models.ResError(err, modelhelper.GroupsCollectionName) } return groups2teams(groups...), nil }
// fetchOne returns only specified team. func (m *MongoDatabase) fetchOne(user, slug string) ([]*Team, error) { group, err := m.adapter.GetGroup(slug) if err == mgo.ErrNotFound { return []*Team{}, nil } else if err != nil { return nil, models.ResError(err, modelhelper.GroupsCollectionName) } switch participant, err := m.adapter.IsParticipant(user, slug); { case err != nil: return nil, models.ResError(err, modelhelper.RelationshipColl) case !participant: return nil, fmt.Errorf("user %q does not belong to %q group", user, slug) } return groups2teams(group), nil }
// BuildStackTemplate fetched stack template details from MongoDB. // // When nil error is returned, the b.StackTemplate field is guaranteed to be non-nil. func (b *Builder) BuildStackTemplate(templateID string) error { var err error if b.StackTemplate, err = modelhelper.GetStackTemplate(templateID); err != nil { return models.ResError(err, "jStackTemplate") } if b.StackTemplate.Template.Content == "" { return errors.New("Stack template content is empty") } return nil }
func (db *mongoDatabase) fetchTeams(perm *MongoPerm) (map[bson.ObjectId]*models.Group, error) { if perm.TeamModel != nil { return map[bson.ObjectId]*models.Group{ perm.TeamModel.Id: perm.TeamModel, }, nil } belongs := modelhelper.Selector{ "targetId": perm.AccModel.Id, "sourceName": "JGroup", "as": "member", } rels, err := modelhelper.GetAllRelationships(belongs) if err != nil { return nil, models.ResError(err, "jRelationship") } ids := make([]bson.ObjectId, len(rels)) for i := range rels { ids[i] = rels[i].SourceId } groups, err := modelhelper.GetGroupsByIds(ids...) if err != nil { return nil, models.ResError(err, "jGroup") } teams := make(map[bson.ObjectId]*models.Group, len(groups)) for i := range groups { teams[groups[i].Id] = groups[i] } return teams, nil }
func (db *mongoDatabase) Validate(f *Filter, c *Cred) (Perm, error) { log := db.log().New("Validate") if err := f.Valid(); err != nil { return nil, err } if f.Matches(c.Perm) { return c.Perm, nil } perm := extractMongoPerm(f, c) log.Debug("extracted perm: %#v", perm) if err := db.fetchModels(f, perm); err != nil { // Return partially constructed perm to allow SetCreds // do alidate-or-create operation. return perm, err } belongs := modelhelper.Selector{ "targetId": perm.CredModel.Id, "sourceId": bson.M{ "$in": perm.CredGroups, }, "as": bson.M{"$in": perm.Roles}, } log.Debug("testing relationship for %+v", belongs) if count, err := modelhelper.RelationshipCount(belongs); err != nil || count == 0 { if err == nil { err = fmt.Errorf("user %q has no access to %q credential", f.Username, c.Ident) } return nil, models.ResError(err, "jRelationship") } if c.Perm == nil { c.Perm = perm } return perm, nil }
// TestWhoami is a kite handler for a "team.whoami" kite method. func (k *Kloud) TeamWhoami(r *kite.Request) (interface{}, error) { opts := &modelhelper.LookupGroupOptions{ Username: r.Username, KiteID: r.Client.ID, ClientURL: r.Client.URL, Environment: r.Client.Environment, } group, err := modelhelper.LookupGroup(opts) if err != nil { return nil, models.ResError(err, "jGroup") } return &WhoamiResponse{ Team: &team.Team{ Name: group.Title, Slug: group.Slug, Privacy: group.Privacy, SubStatus: group.Payment.Subscription.Status, }, }, nil }
// BuildCredentials fetches credential details for current b.Stack from MongoDB. // // When nil error is returned, the b.Koding and b.Credentials fields are non-nil. // // TODO(rjeczalik): Replace with *credential.Client func (b *Builder) BuildCredentials(method, username, groupname string, identifiers []string) error { // fetch jaccount from username account, err := modelhelper.GetAccount(username) if err != nil { return models.ResError(err, "jAccount") } // fetch jUser from username user, err := modelhelper.GetUser(username) if err != nil { return models.ResError(err, "jUser") } kodingMeta := &KodingMeta{ Email: user.Email, Username: user.Name, Nickname: account.Profile.Nickname, Firstname: account.Profile.FirstName, Lastname: account.Profile.LastName, Hash: account.Profile.Hash, } if b.StackTemplate != nil { kodingMeta.TemplateID = b.StackTemplate.Id.Hex() } if b.Stack != nil { kodingMeta.StackID = b.Stack.Stack.Id.Hex() kodingMeta.TemplateID = b.Stack.Stack.BaseStackId.Hex() } groupIDs := []bson.ObjectId{account.Id} if groupname != "" { // fetch jGroup from group slug name group, err := modelhelper.GetGroup(groupname) if err != nil { return models.ResError(err, "jGroup") } // validate if username belongs to groupnam selector := modelhelper.Selector{ "targetId": account.Id, "sourceId": group.Id, "as": bson.M{ "$in": []string{"member"}, }, } count, err := modelhelper.RelationshipCount(selector) if err != nil || count == 0 { return fmt.Errorf("username '%s' does not belong to group '%s'", username, groupname) } kodingMeta.Title = group.Title kodingMeta.Slug = group.Slug groupIDs = append(groupIDs, group.Id) } // 2- fetch credential from identifiers via args credentials, err := modelhelper.GetCredentialsFromIdentifiers(identifiers...) if err != nil { return models.ResError(err, "jCredential") } credentialTitles := make(map[string]string, len(credentials)) for _, cred := range credentials { credentialTitles[cred.Identifier] = cred.Title } // 3- count relationship with credential id and jaccount id as user or // owner. Any non valid credentials will be discarded validKeys := make(map[string]string, len(credentials)) permittedTargets, ok := credPermissions[method] if !ok { return fmt.Errorf("no permission data available for method '%s'", method) } for _, cred := range credentials { selector := modelhelper.Selector{ "targetId": cred.Id, "sourceId": bson.M{ "$in": groupIDs, }, "as": bson.M{"$in": permittedTargets}, } count, err := modelhelper.RelationshipCount(selector) if err != nil { return models.ResError(err, "jRelationship") } if count == 0 { return fmt.Errorf("credential with identifier '%s' is not validated: %v", cred.Identifier, err) } validKeys[cred.Identifier] = cred.Provider } // 5- return list of keys. b.Koding = &stack.Credential{ Provider: "koding", Credential: kodingMeta, } creds := make([]*stack.Credential, 0, len(validKeys)) for ident, provider := range validKeys { creds = append(creds, &stack.Credential{ Title: credentialTitles[ident], Provider: provider, Identifier: ident, }) } if err := b.FetchCredentials(username, creds...); err != nil { // TODO(rjeczalik): add *NotFoundError support to CredStore return models.ResError(err, "jCredentialData") } b.Credentials = append(b.Credentials, creds...) for i, cred := range b.Credentials { b.Log.Debug("Built credential #%d: %# v (%+v, %+v)", i, cred, cred.Credential, cred.Bootstrap) } return nil }
// BuildMachines fetches machines that belongs to existing b.Stack. // // It validates whether user is allowed to perform apply operation. // When nil error is returned, the b.Machines field is non-nil. func (b *Builder) BuildMachines(ctx context.Context) error { ids := b.Stack.Machines sess, ok := session.FromContext(ctx) if !ok { return errors.New("session context is not passed") } req, ok := request.FromContext(ctx) if !ok { return errors.New("request context is not passed") } mongodbIds := make([]bson.ObjectId, len(ids)) for i, id := range ids { mongodbIds[i] = bson.ObjectIdHex(id) } b.Log.Debug("Building machines with IDs: %+v", ids) machines := make([]*models.Machine, 0) if err := sess.DB.Run("jMachines", func(c *mgo.Collection) error { return c.Find(bson.M{"_id": bson.M{"$in": mongodbIds}}).All(&machines) }); err != nil { return err } b.Log.Debug("Fetched machines: %+v", machines) validUsers := make(map[string]models.MachineUser, 0) validMachines := make(map[string]*models.Machine, 0) for _, machine := range machines { // machines with empty users are supposed to allowed by default // (gokmen) if len(machine.Users) == 0 { validMachines[machine.ObjectId.Hex()] = machine continue } // for others we need to be sure they are valid // TODO(arslan): add custom type with custom methods for type // []*Machineuser for _, user := range machine.Users { // we only going to select users that are allowed: // // - team member that owns vm (sudo + owner) // - team admin that owns all vms (owner + !permanent) // // A shared user is (owner + permanent). if (user.Sudo || !user.Permanent) && user.Owner { validUsers[user.Id.Hex()] = user } } } allowedIds := make([]bson.ObjectId, 0) for _, user := range validUsers { allowedIds = append(allowedIds, user.Id) } b.Log.Debug("Building users with allowed IDs: %+v", allowedIds) users, err := modelhelper.GetUsersById(allowedIds...) if err != nil { return models.ResError(err, "jUser") } // find whether requested user is among allowed ones var reqUser *models.User for _, u := range users { if u.Name == req.Username { reqUser = u break } } b.Log.Debug("Found requester: %v (requester username: %s)", reqUser, req.Username) if reqUser != nil { // now check if the requested user is inside the allowed users list for _, m := range machines { for _, user := range m.Users { if user.Id.Hex() == reqUser.ObjectId.Hex() { validMachines[m.ObjectId.Hex()] = m break } } } } if len(validMachines) == 0 { return fmt.Errorf("no valid machines found for the user: %s", req.Username) } b.Machines = make(map[string]*models.Machine, len(validMachines)) for _, m := range validMachines { label := m.Label if s, ok := m.Meta["assignedLabel"].(string); ok { label = s } b.Machines[label] = m } b.Log.Debug("Machines built: %+v", b.Machines) return nil }
// Machines returns all machines stored in MongoDB database that matches a given // filter. func (m *MongoDatabase) Machines(f *Filter) ([]*Machine, error) { if m.adapter == nil { return nil, errors.New("database adapter is unavailable") } if f == nil { return nil, errors.New("machine filter is not set") } if f.Username == "" { return nil, errors.New("machine requires user name to be provided") } // Get all machines that can be seen by provided user. This also includes // shared machines. machinesDB, err := m.adapter.GetParticipatedMachinesByUsername(f.Username) if err != nil { return nil, models.ResError(err, modelhelper.MachinesColl) } // We do not need machines from koding solo(koding provider) so, skip them. for i := 0; i < len(machinesDB); i++ { if machinesDB[i].Provider == modelhelper.MachineProviderKoding { machinesDB = append(machinesDB[:i], machinesDB[i+1:]...) i-- } } // Leave only shared machines that user approved. if f.OnlyApproved { for i := 0; i < len(machinesDB); i++ { for j := range machinesDB[i].Users { if machinesDB[i].Users[j].Username == f.Username && // user of machine !machinesDB[i].Users[j].Owner && // and not an owner !machinesDB[i].Users[j].Approved { // who didn't approve sharing. machinesDB = append(machinesDB[:i], machinesDB[i+1:]...) i-- } } } } // Get stack template IDs used by machines. Using map as the storage will // remove duplicated values. stackTmplIDs := make(map[bson.ObjectId]struct{}) for i := range machinesDB { if machinesDB[i].GeneratedFrom != nil { stackTmplIDs[machinesDB[i].GeneratedFrom.TemplateId] = struct{}{} } } // We don't need to search in jGroups collection in order to find team name // because jStackTemplates also contains that information. stackTmplsDB, err := m.adapter.GetStackTemplateFieldsByIds( toSlice(stackTmplIDs), // stack templates to find. []string{"_id", "group", "title"}, // fields we need from jStackTemplates. ) if err != nil { return nil, models.ResError(err, modelhelper.StackTemplateColl) } // Helper made to simplify searching for group and title names. groupTitles := make(map[bson.ObjectId][2]string, len(stackTmplsDB)) for _, st := range stackTmplsDB { groupTitles[st.Id] = [2]string{st.Group, st.Title} } machines := make([]*Machine, len(machinesDB)) for i, mdb := range machinesDB { machines[i] = &Machine{ ID: mdb.ObjectId.Hex(), Provider: mdb.Provider, Label: mdb.Label, IP: hostOnly(mdb.IpAddress), QueryString: mdb.QueryString, RegisterURL: mdb.RegisterURL, CreatedAt: mdb.CreatedAt, Status: Status{ State: mdb.Status.State, Reason: mdb.Status.Reason, ModifiedAt: mdb.Status.ModifiedAt, }, Users: filterUsers(mdb.Users, f), } if mdb.GeneratedFrom != nil { machines[i].Team = groupTitles[mdb.GeneratedFrom.TemplateId][0] machines[i].Stack = groupTitles[mdb.GeneratedFrom.TemplateId][1] } } return machines, nil }
func (db *mongoDatabase) fetchCreds(f *Filter, acc *models.Account, user *models.User, teams map[bson.ObjectId]*models.Group, belongs modelhelper.Selector, creds *[]*Cred) error { db.log().Debug("fetching credentials for %+v", belongs) rels, err := modelhelper.GetAllRelationships(belongs) if err != nil && err != mgo.ErrNotFound { return models.ResError(err, "jRelationship") } ids := make([]bson.ObjectId, len(rels)) for i := range rels { ids[i] = rels[i].TargetId } c, err := modelhelper.GetCredentialByIDs(ids...) if err == mgo.ErrNotFound { return nil // nothing to fetch, ignore dangling jRelationship } if err != nil { return err } credentials := make(map[bson.ObjectId]*models.Credential, len(c)) for i := range c { credentials[c[i].Id] = c[i] } for _, rel := range rels { cred, ok := credentials[rel.TargetId] if !ok { continue // ignore dangling jRelationship } if f.Provider != "" && cred.Provider != f.Provider { // provider does not contains, filter out continue } c := &Cred{ Ident: cred.Identifier, Provider: cred.Provider, Title: cred.Title, Perm: &MongoPerm{ AccModel: acc, UserModel: user, CredModel: cred, RoleNames: UserRole, }, } db.log().Debug("fetched %+v", c) if team, ok := teams[rel.SourceId]; ok { c.Team = team.Slug c.Perm.(*MongoPerm).TeamModel = team } *creds = append(*creds, c) } return nil }
func (db *mongoDatabase) fetchModels(f *Filter, perm *MongoPerm) (err error) { log := db.Log.New("fetchModels") if perm.AccModel == nil { log.Debug("fetching %q account", f.Username) perm.AccModel, err = modelhelper.GetAccount(f.Username) if err != nil { return models.ResError(err, "jAccount") } perm.CredGroups = append(perm.CredGroups, perm.AccModel.Id) } if perm.UserModel == nil { log.Debug("fetching %q user", f.Username) perm.UserModel, err = modelhelper.GetUser(f.Username) if err != nil { return models.ResError(err, "jUser") } } if f.Teamname != "" { if perm.TeamModel == nil { log.Debug("fetching %q team", f.Teamname) perm.TeamModel, err = modelhelper.GetGroup(f.Teamname) if err != nil { return models.ResError(err, "jGroup") } perm.CredGroups = append(perm.CredGroups, perm.TeamModel.Id) } if !perm.Member { belongs := modelhelper.Selector{ "targetId": perm.AccModel.Id, "sourceId": perm.TeamModel.Id, "as": "member", } log.Debug("testing relationship for %+v", belongs) if count, err := modelhelper.RelationshipCount(belongs); err != nil || count == 0 { if err == nil { err = fmt.Errorf("user %q does not belong to %q group", f.Username, f.Teamname) } return models.ResError(err, "jRelationship") } perm.Member = true } } if perm.CredModel == nil && f.Ident != "" { log.Debug("fetching %q credential", f.Ident) perm.CredModel, err = modelhelper.GetCredential(f.Ident) if err != nil { return models.ResError(err, "jCredential") } } return nil }