package useridentitymapping import ( "fmt" kapi "k8s.io/kubernetes/pkg/api" kerrs "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/runtime" utilruntime "k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/validation/field" "github.com/openshift/origin/pkg/user/api" "github.com/openshift/origin/pkg/user/registry/identity" "github.com/openshift/origin/pkg/user/registry/user" ) // REST implements the RESTStorage interface in terms of an image registry and // image repository registry. It only supports the Create method and is used // to simplify adding a new Image and tag to an ImageRepository. type REST struct { userRegistry user.Registry identityRegistry identity.Registry } // NewREST returns a new REST. func NewREST(userRegistry user.Registry, identityRegistry identity.Registry) *REST { return &REST{userRegistry: userRegistry, identityRegistry: identityRegistry} } // New returns a new UserIdentityMapping for use with Create. func (r *REST) New() runtime.Object { return &api.UserIdentityMapping{} } // Get returns the mapping for the named identity func (s *REST) Get(ctx kapi.Context, name string) (runtime.Object, error) { _, _, _, _, mapping, err := s.getRelatedObjects(ctx, name) return mapping, err } // Create associates a user and identity if they both exist, and the identity is not already mapped to a user func (s *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { mapping, ok := obj.(*api.UserIdentityMapping) if !ok { return nil, kerrs.NewBadRequest("invalid type") } Strategy.PrepareForCreate(mapping) createdMapping, _, err := s.createOrUpdate(ctx, obj, true) return createdMapping, err } // Update associates an identity with a user. // Both the identity and user must already exist. // If the identity is associated with another user already, it is disassociated. func (s *REST) Update(ctx kapi.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { obj, err := objInfo.UpdatedObject(ctx, nil) if err != nil { return nil, false, err } mapping, ok := obj.(*api.UserIdentityMapping) if !ok { return nil, false, kerrs.NewBadRequest("invalid type") } Strategy.PrepareForUpdate(mapping, nil) return s.createOrUpdate(ctx, mapping, false) } func (s *REST) createOrUpdate(ctx kapi.Context, obj runtime.Object, forceCreate bool) (runtime.Object, bool, error) { mapping := obj.(*api.UserIdentityMapping) identity, identityErr, oldUser, oldUserErr, oldMapping, oldMappingErr := s.getRelatedObjects(ctx, mapping.Name) // Ensure we didn't get any errors other than NotFound errors if !(oldMappingErr == nil || kerrs.IsNotFound(oldMappingErr)) { return nil, false, oldMappingErr } if !(identityErr == nil || kerrs.IsNotFound(identityErr)) { return nil, false, identityErr } if !(oldUserErr == nil || kerrs.IsNotFound(oldUserErr)) { return nil, false, oldUserErr } // If we expect to be creating, fail if the mapping already existed if forceCreate && oldMappingErr == nil { return nil, false, kerrs.NewAlreadyExists(api.Resource("useridentitymapping"), oldMapping.Name) } // Allow update to create if missing creating := forceCreate || kerrs.IsNotFound(oldMappingErr) if creating { // Pre-create checks with no access to oldMapping if err := rest.BeforeCreate(Strategy, ctx, mapping); err != nil { return nil, false, err } // Ensure resource version is not specified if len(mapping.ResourceVersion) > 0 { return nil, false, kerrs.NewNotFound(api.Resource("useridentitymapping"), mapping.Name) } } else { // Pre-update checks with access to oldMapping if err := rest.BeforeUpdate(Strategy, ctx, mapping, oldMapping); err != nil { return nil, false, err } // Ensure resource versions match if len(mapping.ResourceVersion) > 0 && mapping.ResourceVersion != oldMapping.ResourceVersion { return nil, false, kerrs.NewConflict(api.Resource("useridentitymapping"), mapping.Name, fmt.Errorf("the resource was updated to %s", oldMapping.ResourceVersion)) } // If we're "updating" to the user we're already pointing to, we're already done if mapping.User.Name == oldMapping.User.Name { return oldMapping, false, nil } } // Validate identity if kerrs.IsNotFound(identityErr) { errs := field.ErrorList{field.Invalid(field.NewPath("identity", "name"), mapping.Identity.Name, "referenced identity does not exist")} return nil, false, kerrs.NewInvalid(api.Kind("UserIdentityMapping"), mapping.Name, errs) } // Get new user newUser, err := s.userRegistry.GetUser(ctx, mapping.User.Name) if kerrs.IsNotFound(err) { errs := field.ErrorList{field.Invalid(field.NewPath("user", "name"), mapping.User.Name, "referenced user does not exist")} return nil, false, kerrs.NewInvalid(api.Kind("UserIdentityMapping"), mapping.Name, errs) } if err != nil { return nil, false, err } // Update the new user to point at the identity. If this fails, Update is re-entrant if addIdentityToUser(identity, newUser) { if _, err := s.userRegistry.UpdateUser(ctx, newUser); err != nil { return nil, false, err } } // Update the identity to point at the new user. If this fails. Update is re-entrant if setIdentityUser(identity, newUser) { if updatedIdentity, err := s.identityRegistry.UpdateIdentity(ctx, identity); err != nil { return nil, false, err } else { identity = updatedIdentity } } // At this point, the mapping for the identity has been updated to the new user // Everything past this point is cleanup // Update the old user to no longer point at the identity. // If this fails, log the error, but continue, because Update is no longer re-entrant if oldUser != nil && removeIdentityFromUser(identity, oldUser) { if _, err := s.userRegistry.UpdateUser(ctx, oldUser); err != nil { utilruntime.HandleError(fmt.Errorf("error removing identity reference %s from user %s: %v", identity.Name, oldUser.Name, err)) } } updatedMapping, err := mappingFor(newUser, identity) return updatedMapping, creating, err } // Delete deletes the user association for the named identity func (s *REST) Delete(ctx kapi.Context, name string) (runtime.Object, error) { identity, _, user, _, _, mappingErr := s.getRelatedObjects(ctx, name) if mappingErr != nil { return nil, mappingErr } // Disassociate the identity with the user first // If this fails, Delete is re-entrant if removeIdentityFromUser(identity, user) { if _, err := s.userRegistry.UpdateUser(ctx, user); err != nil { return nil, err } } // Remove the user association from the identity last. // If this fails, log the error, but continue, because Delete is no longer re-entrant // At this point, the mapping for the identity no longer exists if unsetIdentityUser(identity) { if _, err := s.identityRegistry.UpdateIdentity(ctx, identity); err != nil { utilruntime.HandleError(fmt.Errorf("error removing user reference %s from identity %s: %v", user.Name, identity.Name, err)) } } return &unversioned.Status{Status: unversioned.StatusSuccess}, nil } // getRelatedObjects returns the identity, user, and mapping for the named identity // a nil mappingErr means all objects were retrieved without errors, and correctly reference each other func (s *REST) getRelatedObjects(ctx kapi.Context, name string) ( identity *api.Identity, identityErr error, user *api.User, userErr error, mapping *api.UserIdentityMapping, mappingErr error, ) { // Initialize errors to NotFound identityErr = kerrs.NewNotFound(api.Resource("identity"), name) userErr = kerrs.NewNotFound(api.Resource("user"), "") mappingErr = kerrs.NewNotFound(api.Resource("useridentitymapping"), name) // Get identity identity, identityErr = s.identityRegistry.GetIdentity(ctx, name) if identityErr != nil { return } if !hasUserMapping(identity) { return } // Get user user, userErr = s.userRegistry.GetUser(ctx, identity.User.Name) if userErr != nil { return } // Ensure relational integrity if !identityReferencesUser(identity, user) { return } if !userReferencesIdentity(user, identity) { return } mapping, mappingErr = mappingFor(user, identity) return } // hasUserMapping returns true if the given identity references a user func hasUserMapping(identity *api.Identity) bool { return len(identity.User.Name) > 0 } // identityReferencesUser returns true if the identity's user name and uid match the given user func identityReferencesUser(identity *api.Identity, user *api.User) bool { return identity.User.Name == user.Name && identity.User.UID == user.UID } // userReferencesIdentity returns true if the user's identity list contains the given identity func userReferencesIdentity(user *api.User, identity *api.Identity) bool { return sets.NewString(user.Identities...).Has(identity.Name) } // addIdentityToUser adds the given identity to the user's list of identities // returns true if the user's identity list was modified func addIdentityToUser(identity *api.Identity, user *api.User) bool { identities := sets.NewString(user.Identities...) if identities.Has(identity.Name) { return false } identities.Insert(identity.Name) user.Identities = identities.List() return true } // removeIdentityFromUser removes the given identity from the user's list of identities // returns true if the user's identity list was modified func removeIdentityFromUser(identity *api.Identity, user *api.User) bool { identities := sets.NewString(user.Identities...) if !identities.Has(identity.Name) { return false } identities.Delete(identity.Name) user.Identities = identities.List() return true } // setIdentityUser sets the identity to reference the given user // returns true if the identity's user reference was modified func setIdentityUser(identity *api.Identity, user *api.User) bool { if identityReferencesUser(identity, user) { return false } identity.User = kapi.ObjectReference{ Name: user.Name, UID: user.UID, } return true } // unsetIdentityUser clears the identity's user reference // returns true if the identity's user reference was modified func unsetIdentityUser(identity *api.Identity) bool { if !hasUserMapping(identity) { return false } identity.User = kapi.ObjectReference{} return true } // mappingFor returns a UserIdentityMapping for the given user and identity // The name and resource version of the identity mapping match the identity func mappingFor(user *api.User, identity *api.Identity) (*api.UserIdentityMapping, error) { return &api.UserIdentityMapping{ ObjectMeta: kapi.ObjectMeta{ Name: identity.Name, ResourceVersion: identity.ResourceVersion, UID: identity.UID, }, Identity: kapi.ObjectReference{ Name: identity.Name, UID: identity.UID, }, User: kapi.ObjectReference{ Name: user.Name, UID: user.UID, }, }, nil }