// GetUserIdentity implements external/interfaces/Provider.GetUserIdentity func (p *provider) GetUserIdentity(data *osincli.AccessData) (authapi.UserIdentityInfo, bool, error) { userdata := githubUser{} if _, err := getJSON(githubUserApiURL, data.AccessToken, &userdata); err != nil { return nil, false, err } if userdata.ID == 0 { return nil, false, errors.New("Could not retrieve GitHub id") } if len(p.allowedOrganizations) > 0 { userOrgs, err := getUserOrgs(data.AccessToken) if err != nil { return nil, false, err } if !userOrgs.HasAny(p.allowedOrganizations.List()...) { return nil, false, fmt.Errorf("User %s is not a member of any allowed organizations %v (user is a member of %v)", userdata.Login, p.allowedOrganizations.List(), userOrgs.List()) } } identity := authapi.NewDefaultUserIdentityInfo(p.providerName, fmt.Sprintf("%d", userdata.ID)) if len(userdata.Name) > 0 { identity.Extra[authapi.IdentityDisplayNameKey] = userdata.Name } if len(userdata.Login) > 0 { identity.Extra[authapi.IdentityPreferredUsernameKey] = userdata.Login } if len(userdata.Email) > 0 { identity.Extra[authapi.IdentityEmailKey] = userdata.Email } glog.V(4).Infof("Got identity=%#v", identity) return identity, true, nil }
func (a *Authenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) { a.loadIfNeeded() if len(username) > 255 { username = username[:255] } if strings.Contains(username, ":") { return nil, false, errors.New("Usernames may not contain : characters") } hash, ok := a.usernames[username] if !ok { return nil, false, nil } if ok, err := testPassword(password, hash); !ok || err != nil { return nil, false, err } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, username) user, err := a.mapper.UserFor(identity) if err != nil { return nil, false, fmt.Errorf("Error creating or updating mapping for: %#v due to %v", identity, err) } glog.V(4).Infof("Got userIdentityMapping: %#v", user) return user, true, nil }
func (a *Authenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) { areq := a.client.NewAccessRequest(osincli.PASSWORD, nil) areq.CustomParameters["username"] = username areq.CustomParameters["password"] = password token, err := areq.GetToken() if err != nil { if oerr, ok := err.(*osincli.Error); ok { if oerr.Id == osincli.E_ACCESS_DENIED { return nil, false, nil } } return nil, false, err } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, username) identity.Extra["token"] = token.AccessToken user, err := a.mapper.UserFor(identity) if err != nil { glog.V(4).Infof("Error creating or updating mapping for: %#v due to %v", identity, err) return nil, false, err } glog.V(4).Infof("Got userIdentityMapping: %#v", user) return user, true, nil }
func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { id := headerValue(req.Header, a.config.IDHeaders) if len(id) == 0 { return nil, false, nil } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, id) if email := headerValue(req.Header, a.config.EmailHeaders); len(email) > 0 { identity.Extra[authapi.IdentityEmailKey] = email } if name := headerValue(req.Header, a.config.NameHeaders); len(name) > 0 { identity.Extra[authapi.IdentityDisplayNameKey] = name } if preferredUsername := headerValue(req.Header, a.config.PreferredUsernameHeaders); len(preferredUsername) > 0 { identity.Extra[authapi.IdentityPreferredUsernameKey] = preferredUsername } user, err := a.mapper.UserFor(identity) if err != nil { glog.V(4).Infof("Error creating or updating mapping for: %#v due to %v", identity, err) return nil, false, err } glog.V(4).Infof("Got userIdentityMapping: %#v", user) return user, true, nil }
func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { username := "" for _, header := range a.config.UserNameHeaders { header = strings.TrimSpace(header) if len(header) == 0 { continue } username = req.Header.Get(header) if len(username) != 0 { break } } if len(username) == 0 { return nil, false, nil } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, username) user, err := a.mapper.UserFor(identity) if err != nil { return nil, false, err } glog.V(4).Infof("Got userIdentityMapping: %#v", user) return user, true, nil }
func makeIdentityInfo(providerName, providerUserName string, extra map[string]string) authapi.UserIdentityInfo { info := authapi.NewDefaultUserIdentityInfo("idp", "bob") if extra != nil { info.Extra = extra } return info }
// AuthenticatePassword approves any login attempt with non-blank username and password func (a alwaysAcceptPasswordAuthenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) { if username == "" || password == "" { return nil, false, nil } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, username) user, err := a.identityMapper.UserFor(identity) glog.V(4).Infof("Got userIdentityMapping: %#v", user) if err != nil { return nil, false, fmt.Errorf("Error creating or updating mapping for: %#v due to %v", identity, err) } return user, true, nil }
// AuthenticatePassword approves any login attempt which is successfully validated with Keystone func (a keystonePasswordAuthenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) { defer func() { if e := recover(); e != nil { utilruntime.HandleError(fmt.Errorf("Recovered panic: %v, %s", e, debug.Stack())) } }() // if password is missing, fail authentication immediately if len(password) == 0 { return nil, false, nil } opts := gophercloud.AuthOptions{ IdentityEndpoint: a.url, Username: username, Password: password, DomainName: a.domainName, } // Calling NewClient/Authenticate manually rather than simply calling AuthenticatedClient // in order to pass in a transport object that supports SSL client, err := openstack.NewClient(opts.IdentityEndpoint) if err != nil { glog.Warningf("Failed: Initializing openstack authentication client: %v", err) return nil, false, err } client.HTTPClient = *a.client err = openstack.AuthenticateV3(client, opts) if err != nil { if responseErr, ok := err.(*gophercloud.UnexpectedResponseCodeError); ok { if responseErr.Actual == 401 { return nil, false, nil } } glog.Warningf("Failed: Calling openstack AuthenticateV3: %v", err) return nil, false, err } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, username) user, err := a.identityMapper.UserFor(identity) if err != nil { glog.V(4).Infof("Error creating or updating mapping for: %#v due to %v", identity, err) return nil, false, err } glog.V(4).Infof("Got userIdentityMapping: %#v", user) return user, true, nil }
// AuthenticatePassword approves any login attempt with non-blank username and password func (a alwaysAcceptPasswordAuthenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) { // Since this IDP doesn't validate usernames or passwords, disallow usernames consisting entirely of spaces // Normalize usernames by removing leading/trailing spaces username = strings.TrimSpace(username) if username == "" || password == "" { return nil, false, nil } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, username) user, err := a.identityMapper.UserFor(identity) if err != nil { glog.V(4).Infof("Error creating or updating mapping for: %#v due to %v", identity, err) return nil, false, err } glog.V(4).Infof("Got userIdentityMapping: %#v", user) return user, true, nil }
// GetUserIdentity implements external/interfaces/Provider.GetUserIdentity func (p *provider) GetUserIdentity(data *osincli.AccessData) (authapi.UserIdentityInfo, bool, error) { req, _ := http.NewRequest("GET", p.userAPIURL, nil) req.Header.Set("Authorization", fmt.Sprintf("bearer %s", data.AccessToken)) client := http.DefaultClient if p.transport != nil { client = &http.Client{Transport: p.transport} } res, err := client.Do(req) if err != nil { return nil, false, err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return nil, false, err } userdata := gitlabUser{} err = json.Unmarshal(body, &userdata) if err != nil { return nil, false, err } if userdata.ID == 0 { return nil, false, errors.New("Could not retrieve GitLab id") } identity := authapi.NewDefaultUserIdentityInfo(p.providerName, fmt.Sprintf("%d", userdata.ID)) if len(userdata.Name) > 0 { identity.Extra[authapi.IdentityDisplayNameKey] = userdata.Name } if len(userdata.Username) > 0 { identity.Extra[authapi.IdentityPreferredUsernameKey] = userdata.Username } if len(userdata.Email) > 0 { identity.Extra[authapi.IdentityEmailKey] = userdata.Email } glog.V(4).Infof("Got identity=%#v", identity) return identity, true, nil }
func (f *DefaultLDAPUserIdentityFactory) IdentityFor(user *ldap.Entry) (identity authapi.UserIdentityInfo, err error) { uid := f.Definer.ID(user) if uid == "" { err = fmt.Errorf("Could not retrieve a non-empty value for ID attributes for dn=%q", user.DN) return } id := authapi.NewDefaultUserIdentityInfo(f.ProviderName, uid) // Add optional extra attributes if present if name := f.Definer.Name(user); len(name) != 0 { id.Extra[authapi.IdentityDisplayNameKey] = name } if email := f.Definer.Email(user); len(email) != 0 { id.Extra[authapi.IdentityEmailKey] = email } if prefUser := f.Definer.PreferredUsername(user); len(prefUser) != 0 { id.Extra[authapi.IdentityPreferredUsernameKey] = prefUser } identity = id return }
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):]) } } }
// getIdentity looks up a username in an LDAP server, and attempts to bind to the user's DN using the provided password func (a *Authenticator) getIdentity(username, password string) (authapi.UserIdentityInfo, bool, error) { defer func() { if e := recover(); e != nil { util.HandleError(fmt.Errorf("Recovered panic: %v, %s", e, debug.Stack())) } }() if len(username) == 0 || len(password) == 0 { return nil, false, nil } // Make the connection l, err := a.connect() if err != nil { return nil, false, err } defer l.Close() // If specified, bind the username/password for search phase if len(a.options.BindDN) > 0 { if err := l.Bind(a.options.BindDN, a.options.BindPassword); err != nil { return nil, false, err } } // & together the filter specified in the LDAP options with the user-specific filter filter := fmt.Sprintf("(&%s(%s=%s))", a.options.URL.Filter, ldap.EscapeFilter(a.options.URL.QueryAttribute), ldap.EscapeFilter(username), ) // Build list of attributes to retrieve attrs := util.NewStringSet(a.options.URL.QueryAttribute) attrs.Insert(a.options.AttributeEmail...) attrs.Insert(a.options.AttributeName...) attrs.Insert(a.options.AttributePreferredUsername...) attrs.Insert(a.options.AttributeID...) // Search for LDAP record searchRequest := ldap.NewSearchRequest( a.options.URL.BaseDN, // base dn int(a.options.URL.Scope), // scope ldap.NeverDerefAliases, // deref 2, // size limit, we want to know if this is not unique, but don't want the entire tree 0, // no client-specified time limit, determined by LDAP server. TODO: make configurable? false, // not types only filter, // filter attrs.List(), // attributes to retrieve nil, // controls ) glog.V(4).Infof("searching for %s", filter) results, err := l.Search(searchRequest) if err != nil { return nil, false, err } if len(results.Entries) == 0 { // 0 results means a missing username, not an error glog.V(4).Infof("no entries matching %s", filter) return nil, false, nil } if len(results.Entries) > 1 { // More than 1 result means a misconfigured server filter or query parameter return nil, false, fmt.Errorf("multiple entries found matching %q", username) } entry := results.Entries[0] glog.V(4).Infof("found dn=%q for %s", entry.DN, filter) // Bind with given username and password to attempt to authenticate if err := l.Bind(entry.DN, password); err != nil { glog.V(4).Infof("error binding password for %q: %v", entry.DN, err) if err, ok := err.(*ldap.Error); ok { switch err.ResultCode { case ldap.LDAPResultInappropriateAuthentication: // inappropriateAuthentication (48) // Indicates the server requires the client that had attempted // to bind anonymously or without supplying credentials to // provide some form of credentials. fallthrough case ldap.LDAPResultInvalidCredentials: // invalidCredentials (49) // Indicates that the provided credentials (e.g., the user's name // and password) are invalid. // Authentication failed, return false, but no error return nil, false, nil } } return nil, false, err } // Build the identity uid := getAttributeValue(entry, a.options.AttributeID) if uid == "" { return nil, false, fmt.Errorf("Could not retrieve a non-empty value from %v attributes for dn=%q", a.options.AttributeID, entry.DN) } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, uid) // Add optional extra attributes if present for k, attrs := range map[string][]string{ authapi.IdentityPreferredUsernameKey: a.options.AttributePreferredUsername, authapi.IdentityEmailKey: a.options.AttributeEmail, authapi.IdentityDisplayNameKey: a.options.AttributeName, } { if v := getAttributeValue(entry, attrs); len(v) != 0 { identity.Extra[k] = v } } return identity, true, nil }
// GetUserIdentity implements external/interfaces/Provider.GetUserIdentity func (p provider) GetUserIdentity(data *osincli.AccessData) (authapi.UserIdentityInfo, bool, error) { // Token response MUST include id_token // http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse idToken, ok := data.ResponseData["id_token"].(string) if !ok { return nil, false, fmt.Errorf("No id_token returned in %v", data.ResponseData) } // id_token MUST be a valid JWT idTokenClaims, err := decodeJWT(idToken) if err != nil { return nil, false, err } if p.IDTokenValidator != nil { if err := p.IDTokenValidator(idTokenClaims); err != nil { return nil, false, err } } // TODO: validate JWT // http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation // id_token MUST contain a sub claim as the subject identifier // http://openid.net/specs/openid-connect-core-1_0.html#IDToken idTokenSubject, ok := idTokenClaims[SubjectClaim].(string) if !ok { return nil, false, fmt.Errorf("id_token did not contain a 'sub' claim: %#v", idTokenClaims) } // Use id_token claims by default claims := idTokenClaims // If we have a userinfo URL, use it to get more detailed claims if len(p.UserInfoURL) != 0 { userInfoClaims, err := fetchUserInfo(p.UserInfoURL, data.AccessToken, p.transport) if err != nil { return nil, false, err } // The sub (subject) Claim MUST always be returned in the UserInfo Response. // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse userInfoSubject, ok := userInfoClaims[SubjectClaim].(string) if !ok { return nil, false, fmt.Errorf("userinfo response did not contain a 'sub' claim: %#v", userInfoClaims) } // The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; // if they do not match, the UserInfo Response values MUST NOT be used. // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse if userInfoSubject != idTokenSubject { return nil, false, fmt.Errorf("userinfo 'sub' claim (%s) did not match id_token 'sub' claim (%s)", userInfoSubject, idTokenSubject) } // Merge in userinfo claims in case id_token claims contained some that userinfo did not for k, v := range userInfoClaims { claims[k] = v } } id, _ := getClaimValue(claims, p.IDClaims) if id == "" { return nil, false, fmt.Errorf("Could not retrieve id claim for %#v", p.IDClaims) } identity := authapi.NewDefaultUserIdentityInfo(p.providerName, id) if preferredUsername, _ := getClaimValue(claims, p.PreferredUsernameClaims); len(preferredUsername) != 0 { identity.Extra[authapi.IdentityPreferredUsernameKey] = preferredUsername } if email, _ := getClaimValue(claims, p.EmailClaims); len(email) != 0 { identity.Extra[authapi.IdentityEmailKey] = email } if name, _ := getClaimValue(claims, p.NameClaims); len(name) != 0 { identity.Extra[authapi.IdentityDisplayNameKey] = name } glog.V(4).Infof("identity=%v", identity) return identity, true, nil }
func TestProvision(t *testing.T) { testcases := map[string]struct { ProviderName string ProviderUserName string ExistingIdentity *userapi.Identity ExistingUser *userapi.User NewIdentityGetterResponses []interface{} ExpectedActions []test.Action ExpectedError bool ExpectedUserName string }{ "no identity, create user succeeds": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ makeUser("bobUserUID", "bob", "idp:bob"), }, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, // ... new identity user getter creates user {"CreateIdentity", makeIdentity("", "idp", "bob", "bobUserUID", "bob")}, }, ExpectedUserName: "******", }, "no identity, alreadyexists error retries": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ kerrs.NewAlreadyExists("User", "bob"), makeUser("bobUserUID", "bob", "idp:bob"), }, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error {"GetIdentity", "idp:bob"}, // ... new identity user getter creates user {"CreateIdentity", makeIdentity("", "idp", "bob", "bobUserUID", "bob")}, }, ExpectedUserName: "******", }, "no identity, conflict error retries": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ kerrs.NewConflict("User", "bob", fmt.Errorf("conflict")), makeUser("bobUserUID", "bob", "idp:bob"), }, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error {"GetIdentity", "idp:bob"}, // ... new identity user getter creates user {"CreateIdentity", makeIdentity("", "idp", "bob", "bobUserUID", "bob")}, }, ExpectedUserName: "******", }, "no identity, only retries 3 times": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ kerrs.NewConflict("User", "bob", fmt.Errorf("conflict")), kerrs.NewConflict("User", "bob", fmt.Errorf("conflict")), kerrs.NewConflict("User", "bob", fmt.Errorf("conflict")), kerrs.NewConflict("User", "bob", fmt.Errorf("conflict")), }, ExpectedActions: []test.Action{ // original attempt {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error // retry #1 {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error // retry #2 {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error // retry #3 {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error }, ExpectedError: true, }, "no identity, unknown error does not retry": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: nil, ExistingUser: nil, NewIdentityGetterResponses: []interface{}{ fmt.Errorf("other error"), }, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, // ... new identity user getter returns error }, ExpectedError: true, }, "existing identity, no user reference": { ProviderName: "idp", ProviderUserName: "******", ExistingIdentity: makeIdentity("bobIdentityUID", "idp", "bob", "", ""), ExistingUser: nil, NewIdentityGetterResponses: []interface{}{}, 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, NewIdentityGetterResponses: []interface{}{}, 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"), NewIdentityGetterResponses: []interface{}{}, 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"*/), NewIdentityGetterResponses: []interface{}{}, 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"), NewIdentityGetterResponses: []interface{}{}, ExpectedActions: []test.Action{ {"GetIdentity", "idp:bob"}, {"GetUser", "bob"}, }, 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 } newIdentityUserGetter := &testNewIdentityGetter{responses: tc.NewIdentityGetterResponses} provisionMapper := &provisioningIdentityMapper{ identity: identityRegistry, user: userRegistry, provisioningStrategy: newIdentityUserGetter, } identity := authapi.NewDefaultUserIdentityInfo(tc.ProviderName, tc.ProviderUserName) user, err := provisionMapper.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 } if newIdentityUserGetter.called != len(tc.NewIdentityGetterResponses) { t.Errorf("%s: Expected %d calls to UserForNewIdentity, got %d", k, len(tc.NewIdentityGetterResponses), newIdentityUserGetter.called) } 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):]) } } }
func (a *Authenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) { req, err := http.NewRequest("GET", a.url, nil) if err != nil { return nil, false, err } req.SetBasicAuth(username, password) req.Header.Set("Accept", "application/json") resp, err := a.client.Do(req) if err != nil { return nil, false, err } defer resp.Body.Close() if resp.StatusCode == http.StatusUnauthorized { return nil, false, nil } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, false, err } remoteError := RemoteError{} json.Unmarshal(body, &remoteError) if remoteError.Error != "" { return nil, false, errors.New(remoteError.Error) } if resp.StatusCode != http.StatusOK { return nil, false, fmt.Errorf("An error occurred while authenticating (%d)", resp.StatusCode) } remoteUserData := RemoteUserData{} err = json.Unmarshal(body, &remoteUserData) if err != nil { return nil, false, err } if len(remoteUserData.Subject) == 0 { return nil, false, errors.New("Could not retrieve user data") } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, remoteUserData.Subject) if len(remoteUserData.Name) > 0 { identity.Extra[authapi.IdentityDisplayNameKey] = remoteUserData.Name } if len(remoteUserData.PreferredUsername) > 0 { identity.Extra[authapi.IdentityPreferredUsernameKey] = remoteUserData.PreferredUsername } if len(remoteUserData.Email) > 0 { identity.Extra[authapi.IdentityEmailKey] = remoteUserData.Email } user, err := a.mapper.UserFor(identity) glog.V(4).Infof("Got userIdentityMapping: %#v", user) if err != nil { return nil, false, fmt.Errorf("Error creating or updating mapping for: %#v due to %v", identity, err) } return user, true, nil }
func TestProvisionConflictingIdentity(t *testing.T) { expectedProviderName := "myprovidername" expectedProviderUserName := "******" expectedIdentityName := "myprovidername:myusername" expectedUserName := "******" expectedUserUID := "myuseruid" // Expect identity to fully specify a user name and uid expectedCreateIdentity := &api.Identity{ ObjectMeta: kapi.ObjectMeta{Name: expectedIdentityName}, ProviderName: expectedProviderName, ProviderUserName: expectedProviderUserName, User: kapi.ObjectReference{ Name: expectedUserName, UID: types.UID(expectedUserUID), }, Extra: map[string]string{}, } // Expect user to be populated with the right name, display name, and identity expectedCreateUser := &api.User{ ObjectMeta: kapi.ObjectMeta{ Name: expectedUserName, }, Identities: []string{expectedIdentityName}, } // Return a user containing a uid expectedCreateUserResult := &api.User{ ObjectMeta: kapi.ObjectMeta{ Name: expectedUserName, UID: types.UID(expectedUserUID), }, Identities: []string{expectedIdentityName}, } expectedActions := []test.Action{ {"GetIdentity", expectedIdentityName}, {"GetUser", expectedProviderUserName}, {"GetUser", expectedProviderUserName + "2"}, {"GetUser", expectedProviderUserName + "3"}, {"CreateUser", expectedCreateUser}, {"CreateIdentity", expectedCreateIdentity}, } actions := []test.Action{} identityRegistry := &test.IdentityRegistry{ Create: expectedCreateIdentity, Actions: &actions, } userRegistry := &test.UserRegistry{ Get: map[string]*api.User{ expectedProviderUserName: {}, expectedProviderUserName + "2": {}, }, Create: expectedCreateUserResult, Actions: &actions, } identityMapper := NewAlwaysCreateUserIdentityToUserMapper(identityRegistry, userRegistry) identity := authapi.NewDefaultUserIdentityInfo(expectedProviderName, expectedProviderUserName) identityMapper.UserFor(identity) for i, action := range actions { if len(expectedActions) <= i { t.Fatalf("Expected %d actions, got extras: %#v", len(expectedActions), actions[i:]) } expectedAction := expectedActions[i] if !reflect.DeepEqual(expectedAction, action) { t.Fatalf("Expected\n\t%s %#v\nGot\n\t%s %#v", expectedAction.Name, expectedAction.Object, action.Name, action.Object) } } }