feat: added organizationId/employee id as preferred username
Signed-off-by: Drew Foehn <drew@pixelburn.net>
This commit is contained in:
		
							parent
							
								
									110d51d1d7
								
							
						
					
					
						commit
						4b6d7a2428
					
				|  | @ -11,6 +11,7 @@ | ||||||
| - [#3228](https://github.com/oauth2-proxy/oauth2-proxy/pull/3228) fix: use GetSecret() in ticket.go makeCookie to respect cookie-secret-file (@stagswtf) | - [#3228](https://github.com/oauth2-proxy/oauth2-proxy/pull/3228) fix: use GetSecret() in ticket.go makeCookie to respect cookie-secret-file (@stagswtf) | ||||||
| - [#3244](https://github.com/oauth2-proxy/oauth2-proxy/pull/3244) chore(deps): upgrade to latest go1.25.3 (@tuunit) | - [#3244](https://github.com/oauth2-proxy/oauth2-proxy/pull/3244) chore(deps): upgrade to latest go1.25.3 (@tuunit) | ||||||
| - [#3238](https://github.com/oauth2-proxy/oauth2-proxy/pull/3238) chore: Replace pkg/clock with narrowly targeted stub clocks (@dsymonds) | - [#3238](https://github.com/oauth2-proxy/oauth2-proxy/pull/3238) chore: Replace pkg/clock with narrowly targeted stub clocks (@dsymonds) | ||||||
|  | - [3237](https://github.com/oauth2-proxy/oauth2-proxy/pull/3237) - Option to use organization id for preferred username in Google Provider (@pixeldrew) | ||||||
| 
 | 
 | ||||||
| # V7.12.0 | # V7.12.0 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -252,6 +252,8 @@ Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". | ||||||
| | `serviceAccountJson` | _string_ | ServiceAccountJSON is the path to the service account json credentials | | | `serviceAccountJson` | _string_ | ServiceAccountJSON is the path to the service account json credentials | | ||||||
| | `useApplicationDefaultCredentials` | _bool_ | UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON | | | `useApplicationDefaultCredentials` | _bool_ | UseApplicationDefaultCredentials is a boolean whether to use Application Default Credentials instead of a ServiceAccountJSON | | ||||||
| | `targetPrincipal` | _string_ | TargetPrincipal is the Google Service Account used for Application Default Credentials | | | `targetPrincipal` | _string_ | TargetPrincipal is the Google Service Account used for Application Default Credentials | | ||||||
|  | | `useOrganizationId` | _bool_ | UseOrganizationId indicates whether to use the organization ID as the UserName claim | | ||||||
|  | | `adminApiUserScope` | _string_ | user scope needed for fetching user organization information from admin api, can be one of cloud, user or readonly | | ||||||
| 
 | 
 | ||||||
| ### Header | ### Header | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,13 +5,15 @@ title: Google (default) | ||||||
| 
 | 
 | ||||||
| ## Config Options | ## Config Options | ||||||
| 
 | 
 | ||||||
| | Flag                                           | Toml Field                                   | Type   | Description                                                                                      | Default                                            | | | Flag                                            | Toml Field                                   | Type   | Description                                                                                                                                                                                 | Default                                             | | ||||||
| | ---------------------------------------------- | -------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------ | -------------------------------------------------- | | |-------------------------------------------------|----------------------------------------------| ------ |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------| | ||||||
| | `--google-admin-email`                         | `google_admin_email`                         | string | the google admin to impersonate for api calls                                                    |                                                    | | | `--google-admin-email`                          | `google_admin_email`                         | string | the google admin to impersonate for api calls                                                                                                                                               |                                                     | | ||||||
| | `--google-group`                               | `google_groups`                              | string | restrict logins to members of this google group (may be given multiple times). If not specified and service account or default credentials are configured, all user groups will be allowed.                   |                                                    | | | `--google-group`                                | `google_groups`                              | string | restrict logins to members of this google group (may be given multiple times). If not specified and service account or default credentials are configured, all user groups will be allowed. |                                                     | | ||||||
| | `--google-service-account-json`                | `google_service_account_json`                | string | the path to the service account json credentials                                                 |                                                    | | | `--google-service-account-json`                 | `google_service_account_json`                | string | the path to the service account json credentials                                                                                                                                            |                                                     | | ||||||
| | `--google-use-application-default-credentials` | `google_use_application_default_credentials` | bool   | use application default credentials instead of service account json (i.e. GKE Workload Identity) |                                                    | | | `--google-use-application-default-credentials`  | `google_use_application_default_credentials` | bool   | use application default credentials instead of service account json (i.e. GKE Workload Identity)                                                                                            |                                                     | | ||||||
| | `--google-target-principal`                    | `google_target_principal`                    | bool   | the target principal to impersonate when using ADC                                               | defaults to the service account configured for ADC | | | `--google-target-principal`                     | `google_target_principal`                    | bool   | the target principal to impersonate when using ADC                                                                                                                                          | defaults to the service account configured for ADC  | | ||||||
|  | | `--google-use-organization-id`                  | `google_use_organization_id`                 | bool   | use organization id as preferred username                                                                                                                                                   | false                                               | | ||||||
|  | | `--google-admin-api-user-scope`                 | `google_admin_api_user_scope`                | string | the OAuth scope to use when querying the Google Admin SDK for organization id, can be 'readonly', 'user' or 'cloud'<br/>                                                                    | `readonly`                                          | | ||||||
| 
 | 
 | ||||||
| ## Usage | ## Usage | ||||||
| 
 | 
 | ||||||
|  | @ -73,3 +75,10 @@ can be leveraged through a feature called Workload Identity. Follow Google's [gu | ||||||
| to set up Workload Identity. | to set up Workload Identity. | ||||||
| 
 | 
 | ||||||
| When deployed outside of GCP, [Workload Identity Federation](https://cloud.google.com/docs/authentication/provide-credentials-adc#wlif) might be an option. | When deployed outside of GCP, [Workload Identity Federation](https://cloud.google.com/docs/authentication/provide-credentials-adc#wlif) might be an option. | ||||||
|  | 
 | ||||||
|  | ##### Using Organization ID as Preferred Username (optional) | ||||||
|  | By default, the google provider uses the google id as username. If you would like to use an organization id instead, you can set the `google-use-organization-id` flag to true. | ||||||
|  | This requires that the service account used to query the Google Admin SDK has one of the following scopes granted in step 5 above:  | ||||||
|  | - `https://www.googleapis.com/auth/admin.directory.user.readonly`,  | ||||||
|  | - `https://www.googleapis.com/auth/admin.directory.user`  | ||||||
|  | - `https://www.googleapis.com/auth/cloud-platform`  | ||||||
|  |  | ||||||
|  | @ -510,6 +510,8 @@ type LegacyProvider struct { | ||||||
| 	GoogleServiceAccountJSON               string   `flag:"google-service-account-json" cfg:"google_service_account_json"` | 	GoogleServiceAccountJSON               string   `flag:"google-service-account-json" cfg:"google_service_account_json"` | ||||||
| 	GoogleUseApplicationDefaultCredentials bool     `flag:"google-use-application-default-credentials" cfg:"google_use_application_default_credentials"` | 	GoogleUseApplicationDefaultCredentials bool     `flag:"google-use-application-default-credentials" cfg:"google_use_application_default_credentials"` | ||||||
| 	GoogleTargetPrincipal                  string   `flag:"google-target-principal" cfg:"google_target_principal"` | 	GoogleTargetPrincipal                  string   `flag:"google-target-principal" cfg:"google_target_principal"` | ||||||
|  | 	GoogleUseOrganizationId                bool     `flag:"google-use-organization-id" cfg:"google_use_organization_id"` | ||||||
|  | 	GoogleAdminApiUserScope                string   `flag:"google-admin-api-user-scope" cfg:"google_admin_api_user_scope"` | ||||||
| 
 | 
 | ||||||
| 	// These options allow for other providers besides Google, with
 | 	// These options allow for other providers besides Google, with
 | ||||||
| 	// potential overrides.
 | 	// potential overrides.
 | ||||||
|  | @ -623,6 +625,8 @@ func legacyGoogleFlagSet() *pflag.FlagSet { | ||||||
| 	flagSet.String("google-service-account-json", "", "the path to the service account json credentials") | 	flagSet.String("google-service-account-json", "", "the path to the service account json credentials") | ||||||
| 	flagSet.String("google-use-application-default-credentials", "", "use application default credentials instead of service account json (i.e. GKE Workload Identity)") | 	flagSet.String("google-use-application-default-credentials", "", "use application default credentials instead of service account json (i.e. GKE Workload Identity)") | ||||||
| 	flagSet.String("google-target-principal", "", "the target principal to impersonate when using ADC") | 	flagSet.String("google-target-principal", "", "the target principal to impersonate when using ADC") | ||||||
|  | 	flagSet.String("google-use-organization-id", "", "use organization id as preferred username") | ||||||
|  | 	flagSet.String("google-admin-api-user-scope", "", "authorization scope required to call users.get, can be one of ") | ||||||
| 
 | 
 | ||||||
| 	return flagSet | 	return flagSet | ||||||
| } | } | ||||||
|  | @ -770,6 +774,8 @@ func (l *LegacyProvider) convert() (Providers, error) { | ||||||
| 			ServiceAccountJSON:               l.GoogleServiceAccountJSON, | 			ServiceAccountJSON:               l.GoogleServiceAccountJSON, | ||||||
| 			UseApplicationDefaultCredentials: l.GoogleUseApplicationDefaultCredentials, | 			UseApplicationDefaultCredentials: l.GoogleUseApplicationDefaultCredentials, | ||||||
| 			TargetPrincipal:                  l.GoogleTargetPrincipal, | 			TargetPrincipal:                  l.GoogleTargetPrincipal, | ||||||
|  | 			UseOrganizationId:                l.GoogleUseOrganizationId, | ||||||
|  | 			AdminApiUserScope:                l.GoogleAdminApiUserScope, | ||||||
| 		} | 		} | ||||||
| 	case "entra-id": | 	case "entra-id": | ||||||
| 		provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{ | 		provider.MicrosoftEntraIDConfig = MicrosoftEntraIDOptions{ | ||||||
|  |  | ||||||
|  | @ -230,6 +230,10 @@ type GoogleOptions struct { | ||||||
| 	UseApplicationDefaultCredentials bool `json:"useApplicationDefaultCredentials,omitempty"` | 	UseApplicationDefaultCredentials bool `json:"useApplicationDefaultCredentials,omitempty"` | ||||||
| 	// TargetPrincipal is the Google Service Account used for Application Default Credentials
 | 	// TargetPrincipal is the Google Service Account used for Application Default Credentials
 | ||||||
| 	TargetPrincipal string `json:"targetPrincipal,omitempty"` | 	TargetPrincipal string `json:"targetPrincipal,omitempty"` | ||||||
|  | 	// UseOrganizationId indicates whether to use the organization ID as the UserName claim
 | ||||||
|  | 	UseOrganizationId bool `json:"useOrganizationId,omitempty"` | ||||||
|  | 	// admin scope needed for fetching user organization information from admin api, can be one of cloud, user or defaults to readonly
 | ||||||
|  | 	AdminApiUserScope string `json:"adminApiUserScope,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type OIDCOptions struct { | type OIDCOptions struct { | ||||||
|  |  | ||||||
|  | @ -40,6 +40,8 @@ type GoogleProvider struct { | ||||||
| 	// Refresh. `Authorize` uses the results of this saved in `session.Groups`
 | 	// Refresh. `Authorize` uses the results of this saved in `session.Groups`
 | ||||||
| 	// Since it is called on every request.
 | 	// Since it is called on every request.
 | ||||||
| 	groupValidator func(*sessions.SessionState) bool | 	groupValidator func(*sessions.SessionState) bool | ||||||
|  | 
 | ||||||
|  | 	setPreferredUsername func(s *sessions.SessionState) error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var _ Provider = (*GoogleProvider)(nil) | var _ Provider = (*GoogleProvider)(nil) | ||||||
|  | @ -100,17 +102,59 @@ func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProv | ||||||
| 		groupValidator: func(*sessions.SessionState) bool { | 		groupValidator: func(*sessions.SessionState) bool { | ||||||
| 			return true | 			return true | ||||||
| 		}, | 		}, | ||||||
|  | 
 | ||||||
|  | 		setPreferredUsername: func(state *sessions.SessionState) error { | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials { | 	if opts.UseOrganizationId || opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials { | ||||||
| 		provider.configureGroups(opts) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
|  | 		// reuse admin service to avoid multiple calls for token
 | ||||||
|  | 		var adminService *admin.Service | ||||||
|  | 
 | ||||||
|  | 		if opts.UseOrganizationId { | ||||||
|  | 			// add user scopes to admin api
 | ||||||
|  | 			userScope := getAdminApiUserScope(opts.AdminApiUserScope) | ||||||
|  | 			for index, scope := range possibleScopesList { | ||||||
|  | 				possibleScopesList[index] = scope + " " + userScope | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			adminService = getAdminService(opts) | ||||||
|  | 
 | ||||||
|  | 			provider.setPreferredUsername = func(s *sessions.SessionState) error { | ||||||
|  | 				userName, err := getUserInfo(adminService, s.Email) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 				s.PreferredUsername = userName | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials { | ||||||
|  | 			if adminService == nil { | ||||||
|  | 				adminService = getAdminService(opts) | ||||||
|  | 			} | ||||||
|  | 			provider.configureGroups(opts, adminService) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
| 	return provider, nil | 	return provider, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *GoogleProvider) configureGroups(opts options.GoogleOptions) { | // by default can be readonly user scope
 | ||||||
| 	adminService := getAdminService(opts) | func getAdminApiUserScope(adminApiUserScope string) string { | ||||||
|  | 	switch adminApiUserScope { | ||||||
|  | 	case "cloud": | ||||||
|  | 		return admin.CloudPlatformScope | ||||||
|  | 	case "user": | ||||||
|  | 		return admin.AdminDirectoryUserScope | ||||||
|  | 	} | ||||||
|  | 	return admin.AdminDirectoryUserReadonlyScope | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *GoogleProvider) configureGroups(opts options.GoogleOptions, adminService *admin.Service) { | ||||||
| 	// Backwards compatibility with `--google-group` option
 | 	// Backwards compatibility with `--google-group` option
 | ||||||
| 	if len(opts.Groups) > 0 { | 	if len(opts.Groups) > 0 { | ||||||
| 		p.setAllowedGroups(opts.Groups) | 		p.setAllowedGroups(opts.Groups) | ||||||
|  | @ -204,6 +248,7 @@ func (p *GoogleProvider) Redeem(ctx context.Context, redirectURL, code, codeVeri | ||||||
| 
 | 
 | ||||||
| // EnrichSession checks the listed Google Groups configured and adds any
 | // EnrichSession checks the listed Google Groups configured and adds any
 | ||||||
| // that the user is a member of to session.Groups.
 | // that the user is a member of to session.Groups.
 | ||||||
|  | // if preferred username is configured to be organization ID, it sets that as well.
 | ||||||
| func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionState) error { | func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionState) error { | ||||||
| 	// TODO (@NickMeves) - Move to pure EnrichSession logic and stop
 | 	// TODO (@NickMeves) - Move to pure EnrichSession logic and stop
 | ||||||
| 	// reusing legacy `groupValidator`.
 | 	// reusing legacy `groupValidator`.
 | ||||||
|  | @ -212,7 +257,7 @@ func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionSta | ||||||
| 	// populating logic.
 | 	// populating logic.
 | ||||||
| 	p.groupValidator(s) | 	p.groupValidator(s) | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return p.setPreferredUsername(s) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetGroupRestriction configures the GoogleProvider to restrict access to the
 | // SetGroupRestriction configures the GoogleProvider to restrict access to the
 | ||||||
|  | @ -262,7 +307,7 @@ func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope | ||||||
| 	if opts.UseApplicationDefaultCredentials { | 	if opts.UseApplicationDefaultCredentials { | ||||||
| 		ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{ | 		ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{ | ||||||
| 			TargetPrincipal: getTargetPrincipal(ctx, opts), | 			TargetPrincipal: getTargetPrincipal(ctx, opts), | ||||||
| 			Scopes:          []string{scope}, | 			Scopes:          strings.Split(scope, " "), | ||||||
| 			Subject:         opts.AdminEmail, | 			Subject:         opts.AdminEmail, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | @ -364,6 +409,30 @@ func getTargetPrincipal(ctx context.Context, opts options.GoogleOptions) (target | ||||||
| 	return targetPrincipal | 	return targetPrincipal | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func getUserInfo(service *admin.Service, email string) (string, error) { | ||||||
|  | 	req := service.Users.Get(email) | ||||||
|  | 	user, err := req.Do() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", fmt.Errorf("failed to get user details for %s: %v", email, err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ext, _ := user.ExternalIds.([]interface{}) | ||||||
|  | 	for _, v := range ext { | ||||||
|  | 		m, _ := v.(map[string]interface{}) | ||||||
|  | 		if m == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if t, _ := m["type"].(string); t != "organization" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if val, _ := m["value"].(string); val != "" { | ||||||
|  | 			return val, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return "", fmt.Errorf("failed to get organization id for %s", email) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // getUserGroups retrieves all groups that a user is a member of using the Google Admin Directory API
 | // getUserGroups retrieves all groups that a user is a member of using the Google Admin Directory API
 | ||||||
| func getUserGroups(service *admin.Service, email string) ([]string, error) { | func getUserGroups(service *admin.Service, email string) ([]string, error) { | ||||||
| 	var allGroups []string | 	var allGroups []string | ||||||
|  |  | ||||||
|  | @ -325,3 +325,83 @@ func TestGoogleProvider_getUserGroups(t *testing.T) { | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
| 	assert.Equal(t, []string{"group1@example.com", "group2@example.com"}, groups) | 	assert.Equal(t, []string{"group1@example.com", "group2@example.com"}, groups) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestGoogleProvider_getUserInfo(t *testing.T) { | ||||||
|  | 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		if r.URL.Path == "/admin/directory/v1/users/test@example.com" { | ||||||
|  | 			response := `{ | ||||||
|  | 			  "kind": "admin#directory#user", | ||||||
|  | 			  "id": "", | ||||||
|  | 			  "etag": "\"\"", | ||||||
|  | 			  "primaryEmail": "test@example.com", | ||||||
|  | 			  "name": { | ||||||
|  | 				"givenName": "Test", | ||||||
|  | 				"familyName": "User", | ||||||
|  | 				"fullName": "Test User" | ||||||
|  | 			  }, | ||||||
|  | 			  "isAdmin": false, | ||||||
|  | 			  "isDelegatedAdmin": false, | ||||||
|  | 			  "lastLoginTime": "", | ||||||
|  | 			  "creationTime": "", | ||||||
|  | 			  "agreedToTerms": true, | ||||||
|  | 			  "suspended": false, | ||||||
|  | 			  "archived": false, | ||||||
|  | 			  "changePasswordAtNextLogin": false, | ||||||
|  | 			  "ipWhitelisted": false, | ||||||
|  | 			  "emails": [ | ||||||
|  | 				{ | ||||||
|  | 				  "address": "test@example.com", | ||||||
|  | 				  "primary": true | ||||||
|  | 				} | ||||||
|  | 			  ], | ||||||
|  | 			  "externalIds": [ | ||||||
|  | 				{ | ||||||
|  | 				  "value": "test.user", | ||||||
|  | 				  "type": "organization" | ||||||
|  | 				} | ||||||
|  | 			  ], | ||||||
|  | 			  "organizations": [ | ||||||
|  | 			  ], | ||||||
|  | 			  "phones": [ | ||||||
|  | 			  ], | ||||||
|  | 			  "languages": [ | ||||||
|  | 				{ | ||||||
|  | 				  "languageCode": "en", | ||||||
|  | 				  "preference": "preferred" | ||||||
|  | 				} | ||||||
|  | 			  ], | ||||||
|  | 			  "aliases": [ | ||||||
|  | 				"test.user@example.com" | ||||||
|  | 			  ], | ||||||
|  | 			  "nonEditableAliases": [ | ||||||
|  | 				"test.user@example.com" | ||||||
|  | 			  ], | ||||||
|  | 			  "gender": { | ||||||
|  | 				"type": "male" | ||||||
|  | 			  }, | ||||||
|  | 			  "customerId": "", | ||||||
|  | 			  "orgUnitPath": "/", | ||||||
|  | 			  "isMailboxSetup": true, | ||||||
|  | 			  "isEnrolledIn2Sv": true, | ||||||
|  | 			  "isEnforcedIn2Sv": false, | ||||||
|  | 			  "includeInGlobalAddressList": true, | ||||||
|  | 			  "thumbnailPhotoUrl": "", | ||||||
|  | 			  "thumbnailPhotoEtag": "\"\"", | ||||||
|  | 			  "recoveryEmail": "test.user@gmail.com", | ||||||
|  | 			  "recoveryPhone": "+55555555555" | ||||||
|  | 			}` | ||||||
|  | 			fmt.Fprintln(w, response) | ||||||
|  | 		} else { | ||||||
|  | 			http.NotFound(w, r) | ||||||
|  | 		} | ||||||
|  | 	})) | ||||||
|  | 	defer ts.Close() | ||||||
|  | 
 | ||||||
|  | 	client := &http.Client{} | ||||||
|  | 	adminService, err := admin.NewService(context.Background(), option.WithHTTPClient(client), option.WithEndpoint(ts.URL)) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 	info, err := getUserInfo(adminService, "test@example.com") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, "test.user", info) | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue