// NewIdentityUserMapper returns a UserIdentityMapper that does the following: // 1. Returns an existing user if the identity exists and is associated with an existing user // 2. Returns an error if the identity exists and is not associated with a user (or is associated with a missing user) // 3. Handles new identities according to the requested method func NewIdentityUserMapper(identities identityregistry.Registry, users userregistry.Registry, method MappingMethodType) (authapi.UserIdentityMapper, error) { // initUser initializes fields in a User API object from its associated Identity // called when adding the first Identity to a User (during create or update of a User) initUser := user.NewDefaultUserInitStrategy() switch method { case MappingMethodLookup: mappingStorage := mappingregistry.NewREST(users, identities) mappingRegistry := mappingregistry.NewRegistry(mappingStorage) return &lookupIdentityMapper{mappingRegistry, users}, nil case MappingMethodClaim: return &provisioningIdentityMapper{identities, users, NewStrategyClaim(users, initUser)}, nil case MappingMethodAdd: return &provisioningIdentityMapper{identities, users, NewStrategyAdd(users, initUser)}, nil case MappingMethodGenerate: return &provisioningIdentityMapper{identities, users, NewStrategyGenerate(users, initUser)}, nil default: return nil, fmt.Errorf("unsupported mapping method %q", method) } }
func TestUserInitialization(t *testing.T) { masterConfig, clusterAdminKubeConfig, err := testutil.StartTestMaster() if err != nil { t.Fatalf("unexpected error: %v", err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } etcdClient, err := etcd.GetAndTestEtcdClient(masterConfig.EtcdClientInfo) if err != nil { t.Errorf("unexpected error: %v", err) } etcdHelper, err := origin.NewEtcdStorage(etcdClient, masterConfig.EtcdStorageConfig.OpenShiftStorageVersion, masterConfig.EtcdStorageConfig.OpenShiftStoragePrefix) if err != nil { t.Errorf("unexpected error: %v", err) } userRegistry := userregistry.NewRegistry(useretcd.NewREST(etcdHelper)) identityRegistry := identityregistry.NewRegistry(identityetcd.NewREST(etcdHelper)) useridentityMappingRegistry := useridentitymapping.NewRegistry(useridentitymapping.NewREST(userRegistry, identityRegistry)) lookup := identitymapper.NewLookupIdentityMapper(useridentityMappingRegistry, userRegistry) provisioner := identitymapper.NewAlwaysCreateUserIdentityToUserMapper(identityRegistry, userRegistry) testcases := map[string]struct { Identity authapi.UserIdentityInfo Mapper authapi.UserIdentityMapper CreateIdentity *api.Identity CreateUser *api.User CreateMapping *api.UserIdentityMapping UpdateUser *api.User ExpectedErr error ExpectedUserName string ExpectedFullName string }{ "lookup missing identity": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: lookup, ExpectedErr: kerrs.NewNotFound("UserIdentityMapping", "idp:bob"), }, "lookup existing identity": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: lookup, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), ExpectedUserName: "******", }, "provision missing identity and user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: provisioner, ExpectedUserName: "******", }, "provision missing identity and user with preferred username and display name": { Identity: makeIdentityInfo("idp", "bob", map[string]string{authapi.IdentityDisplayNameKey: "Bob, Sr.", authapi.IdentityPreferredUsernameKey: "admin"}), Mapper: provisioner, ExpectedUserName: "******", ExpectedFullName: "Bob, Sr.", }, "provision missing identity for existing user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: provisioner, CreateUser: makeUser("bob", "idp:bob"), ExpectedUserName: "******", }, "provision missing identity with conflicting user": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: provisioner, CreateUser: makeUser("bob"), ExpectedUserName: "******", }, "provision missing identity with conflicting user and preferred username": { Identity: makeIdentityInfo("idp", "bob", map[string]string{authapi.IdentityPreferredUsernameKey: "admin"}), Mapper: provisioner, CreateUser: makeUser("admin"), ExpectedUserName: "******", }, "provision with existing unmapped identity": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: provisioner, CreateIdentity: makeIdentity("idp", "bob"), ExpectedErr: kerrs.NewNotFound("UserIdentityMapping", "idp:bob"), }, "provision with existing mapped identity with invalid user UID": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: provisioner, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentityWithUserReference("idp", "bob", "mappeduser", "invalidUID"), ExpectedErr: kerrs.NewNotFound("UserIdentityMapping", "idp:bob"), }, "provision with existing mapped identity without user backreference": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: provisioner, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), // Update user to a version which does not reference the identity UpdateUser: makeUser("mappeduser"), ExpectedErr: kerrs.NewNotFound("UserIdentityMapping", "idp:bob"), }, "provision returns existing mapping": { Identity: makeIdentityInfo("idp", "bob", nil), Mapper: provisioner, CreateUser: makeUser("mappeduser"), CreateIdentity: makeIdentity("idp", "bob"), CreateMapping: makeMapping("mappeduser", "idp:bob"), ExpectedUserName: "******", }, } for k, testcase := range testcases { // Cleanup if err := etcdHelper.RecursiveDelete(useretcd.EtcdPrefix, true); err != nil && !etcdstorage.IsEtcdNotFound(err) { t.Fatalf("Could not clean up users: %v", err) } if err := etcdHelper.RecursiveDelete(identityetcd.EtcdPrefix, true); err != nil && !etcdstorage.IsEtcdNotFound(err) { t.Fatalf("Could not clean up identities: %v", err) } // Pre-create items if testcase.CreateUser != nil { _, err := clusterAdminClient.Users().Create(testcase.CreateUser) if err != nil { t.Errorf("%s: Could not create user: %v", k, err) continue } } if testcase.CreateIdentity != nil { _, err := clusterAdminClient.Identities().Create(testcase.CreateIdentity) if err != nil { t.Errorf("%s: Could not create identity: %v", k, err) continue } } if testcase.CreateMapping != nil { _, err := clusterAdminClient.UserIdentityMappings().Update(testcase.CreateMapping) if err != nil { t.Errorf("%s: Could not create mapping: %v", k, err) continue } } if testcase.UpdateUser != nil { if testcase.UpdateUser.ResourceVersion == "" { existingUser, err := clusterAdminClient.Users().Get(testcase.UpdateUser.Name) if err != nil { t.Errorf("%s: Could not get user to update: %v", k, err) continue } testcase.UpdateUser.ResourceVersion = existingUser.ResourceVersion } _, err := clusterAdminClient.Users().Update(testcase.UpdateUser) if err != nil { t.Errorf("%s: Could not update user: %v", k, err) continue } } // Spawn 5 simultaneous mappers to test race conditions var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() userInfo, err := testcase.Mapper.UserFor(testcase.Identity) if err != nil { if testcase.ExpectedErr == nil { t.Errorf("%s: Expected success, got error '%v'", k, err) } else if err.Error() != testcase.ExpectedErr.Error() { t.Errorf("%s: Expected error %v, got '%v'", k, testcase.ExpectedErr.Error(), err) } return } if err == nil && testcase.ExpectedErr != nil { t.Errorf("%s: Expected error '%v', got none", k, testcase.ExpectedErr) return } if userInfo.GetName() != testcase.ExpectedUserName { t.Errorf("%s: Expected username %s, got %s", k, testcase.ExpectedUserName, userInfo.GetName()) return } user, err := clusterAdminClient.Users().Get(userInfo.GetName()) if err != nil { t.Errorf("%s: Error getting user: %v", k, err) } if user.FullName != testcase.ExpectedFullName { t.Errorf("%s: Expected full name %s, got %s", k, testcase.ExpectedFullName, user.FullName) } }() } wg.Wait() } }
func TestLookup(t *testing.T) { testcases := map[string]struct { ProviderName string ProviderUserName string ExistingIdentity *userapi.Identity ExistingUser *userapi.User ExpectedActions []test.Action ExpectedError bool ExpectedUserName string }{ "no identity": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, }, ExpectedError: true, }, "existing identity, no user reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "", ""), ExistingUser: nil, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, }, ExpectedError: true, }, "existing identity, missing user reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "bobUserUID", "bob"), ExistingUser: nil, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, }, ExpectedError: true, }, "existing identity, invalid user UID reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "bobUserUIDInvalid", "bob"), ExistingUser: makeUser("bobUserUID", "bob", "idp:bob"), ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, }, ExpectedError: true, }, "existing identity, user reference without identity backreference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "bobUserUID", "bob"), ExistingUser: makeUser("bobUserUID", "bob" /*, "idp:bob"*/), ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, }, ExpectedError: true, }, "existing identity, user reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "bobUserUID", "bob"), ExistingUser: makeUser("bobUserUID", "bob", "idp:bob"), ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, {"GetUser", "bob"}, // extra request is for group lookup }, ExpectedUserName: "******", }, } for k, tc := range testcases { actions := []test.Action{} identityRegistry := &test.IdentityRegistry{ Get: map[string]*api.Identity{}, Actions: &actions, } userRegistry := &test.UserRegistry{ Get: map[string]*api.User{}, Actions: &actions, } if tc.ExistingIdentity != nil { identityRegistry.Get[tc.ExistingIdentity.Name] = tc.ExistingIdentity } if tc.ExistingUser != nil { userRegistry.Get[tc.ExistingUser.Name] = tc.ExistingUser } mappingStorage := mappingregistry.NewREST(userRegistry, identityRegistry) mappingRegistry := mappingregistry.NewRegistry(mappingStorage) lookupMapper := &lookupIdentityMapper{ mappings: mappingRegistry, users: userRegistry, } identity := authapi.NewDefaultUserIdentityInfo(tc.ProviderName, tc.ProviderUserName) user, err := lookupMapper.UserFor(identity) if tc.ExpectedError != (err != nil) { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedError, err) continue } if !tc.ExpectedError && user.GetName() != tc.ExpectedUserName { t.Errorf("%s: Expected username %v, got %v", k, tc.ExpectedUserName, user.GetName()) continue } for i, action := range actions { if len(tc.ExpectedActions) <= i { t.Fatalf("%s: expected %d actions, got extras: %#v", k, len(tc.ExpectedActions), actions[i:]) continue } expectedAction := tc.ExpectedActions[i] if !reflect.DeepEqual(expectedAction, action) { t.Fatalf("%s: expected\n\t%s %#v\nGot\n\t%s %#v", k, expectedAction.Name, expectedAction.Object, action.Name, action.Object) continue } } if len(actions) < len(tc.ExpectedActions) { t.Errorf("Missing %d additional actions:\n\t%#v", len(tc.ExpectedActions)-len(actions), tc.ExpectedActions[len(actions):]) } } }