OAuth: Introduce user_refresh_token setting and make it default for the selected providers (#71533)
* First changes * WIP docs * Align current tests * Add test for UseRefreshToken * Update docs * Fix * Remove unnecessary AuthCodeURL from generic_oauth * Change GitHub to disable use_refresh_token by default
This commit is contained in:
@@ -600,6 +600,8 @@ tls_skip_verify_insecure = false
|
|||||||
tls_client_cert =
|
tls_client_cert =
|
||||||
tls_client_key =
|
tls_client_key =
|
||||||
tls_client_ca =
|
tls_client_ca =
|
||||||
|
# GitHub OAuth apps does not provide refresh tokens and the access tokens never expires.
|
||||||
|
use_refresh_token = false
|
||||||
|
|
||||||
#################################### GitLab Auth #########################
|
#################################### GitLab Auth #########################
|
||||||
[auth.gitlab]
|
[auth.gitlab]
|
||||||
@@ -625,6 +627,7 @@ tls_client_cert =
|
|||||||
tls_client_key =
|
tls_client_key =
|
||||||
tls_client_ca =
|
tls_client_ca =
|
||||||
use_pkce = true
|
use_pkce = true
|
||||||
|
use_refresh_token = true
|
||||||
|
|
||||||
#################################### Google Auth #########################
|
#################################### Google Auth #########################
|
||||||
[auth.google]
|
[auth.google]
|
||||||
@@ -647,6 +650,7 @@ tls_client_cert =
|
|||||||
tls_client_key =
|
tls_client_key =
|
||||||
tls_client_ca =
|
tls_client_ca =
|
||||||
use_pkce = true
|
use_pkce = true
|
||||||
|
use_refresh_token = true
|
||||||
|
|
||||||
#################################### Grafana.com Auth ####################
|
#################################### Grafana.com Auth ####################
|
||||||
# legacy key names (so they work in env variables)
|
# legacy key names (so they work in env variables)
|
||||||
@@ -657,6 +661,7 @@ client_id = some_id
|
|||||||
client_secret =
|
client_secret =
|
||||||
scopes = user:email
|
scopes = user:email
|
||||||
allowed_organizations =
|
allowed_organizations =
|
||||||
|
use_refresh_token = false
|
||||||
|
|
||||||
[auth.grafana_com]
|
[auth.grafana_com]
|
||||||
name = Grafana.com
|
name = Grafana.com
|
||||||
@@ -669,6 +674,7 @@ client_secret =
|
|||||||
scopes = user:email
|
scopes = user:email
|
||||||
allowed_organizations =
|
allowed_organizations =
|
||||||
skip_org_role_sync = false
|
skip_org_role_sync = false
|
||||||
|
use_refresh_token = false
|
||||||
|
|
||||||
#################################### Azure AD OAuth #######################
|
#################################### Azure AD OAuth #######################
|
||||||
[auth.azuread]
|
[auth.azuread]
|
||||||
@@ -694,6 +700,7 @@ tls_client_key =
|
|||||||
tls_client_ca =
|
tls_client_ca =
|
||||||
use_pkce = true
|
use_pkce = true
|
||||||
skip_org_role_sync = false
|
skip_org_role_sync = false
|
||||||
|
use_refresh_token = true
|
||||||
|
|
||||||
#################################### Okta OAuth #######################
|
#################################### Okta OAuth #######################
|
||||||
[auth.okta]
|
[auth.okta]
|
||||||
@@ -719,6 +726,7 @@ tls_client_cert =
|
|||||||
tls_client_key =
|
tls_client_key =
|
||||||
tls_client_ca =
|
tls_client_ca =
|
||||||
use_pkce = true
|
use_pkce = true
|
||||||
|
use_refresh_token = false
|
||||||
|
|
||||||
#################################### Generic OAuth #######################
|
#################################### Generic OAuth #######################
|
||||||
[auth.generic_oauth]
|
[auth.generic_oauth]
|
||||||
@@ -756,6 +764,7 @@ use_pkce = false
|
|||||||
auth_style =
|
auth_style =
|
||||||
allow_assign_grafana_admin = false
|
allow_assign_grafana_admin = false
|
||||||
skip_org_role_sync = false
|
skip_org_role_sync = false
|
||||||
|
use_refresh_token = false
|
||||||
|
|
||||||
#################################### Basic Auth ##########################
|
#################################### Basic Auth ##########################
|
||||||
[auth.basic]
|
[auth.basic]
|
||||||
|
|||||||
+4
-1
@@ -21,6 +21,7 @@ The Azure AD authentication allows you to use an Azure Active Directory tenant a
|
|||||||
- [Assign server administrator privileges](#assign-server-administrator-privileges)
|
- [Assign server administrator privileges](#assign-server-administrator-privileges)
|
||||||
- [Enable Azure AD OAuth in Grafana](#enable-azure-ad-oauth-in-grafana)
|
- [Enable Azure AD OAuth in Grafana](#enable-azure-ad-oauth-in-grafana)
|
||||||
- [Configure refresh token](#configure-refresh-token)
|
- [Configure refresh token](#configure-refresh-token)
|
||||||
|
- [Configure allowed tenants](#configure-allowed-tenants)
|
||||||
- [Configure allowed groups](#configure-allowed-groups)
|
- [Configure allowed groups](#configure-allowed-groups)
|
||||||
- [Configure allowed domains](#configure-allowed-domains)
|
- [Configure allowed domains](#configure-allowed-domains)
|
||||||
- [PKCE](#pkce)
|
- [PKCE](#pkce)
|
||||||
@@ -176,7 +177,9 @@ When a user logs in using an OAuth provider, Grafana verifies that the access to
|
|||||||
|
|
||||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||||
|
|
||||||
To enable a refresh token for AzureAD, extend the `scopes` in `[auth.azuread]` with `offline_access`.
|
Refresh token fetching and access token expiration check is enabled by default for the AzureAD provider since Grafana v10.1.0 if the `accessTokenExpirationCheck` feature toggle is enabled. If you would like to disable access token expiration check then set the `use_refresh_token` configuration value to `false`.
|
||||||
|
|
||||||
|
> **Note:** The `accessTokenExpirationCheck` feature toggle will be removed in Grafana v10.2.0 and the `use_refresh_token` configuration value will be used instead for configuring refresh token fetching and access token expiration check.
|
||||||
|
|
||||||
### Configure allowed tenants
|
### Configure allowed tenants
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -65,7 +65,9 @@ To integrate your OAuth2 provider with Grafana using our generic OAuth2 authenti
|
|||||||
|
|
||||||
b. Extend the `scopes` field of `[auth.generic_oauth]` section in Grafana configuration file with refresh token scope used by your OAuth2 provider.
|
b. Extend the `scopes` field of `[auth.generic_oauth]` section in Grafana configuration file with refresh token scope used by your OAuth2 provider.
|
||||||
|
|
||||||
c. Enable the refresh token on the provider if required.
|
c. Set `use_refresh_token` to `true` in `[auth.generic_oauth]` section in Grafana configuration file.
|
||||||
|
|
||||||
|
d. Enable the refresh token on the provider if required.
|
||||||
|
|
||||||
1. [Configure role mapping]({{< relref "#configure-role-mapping" >}}).
|
1. [Configure role mapping]({{< relref "#configure-role-mapping" >}}).
|
||||||
1. Optional: [Configure team synchronization]({{< relref "#configure-team-synchronization" >}}).
|
1. Optional: [Configure team synchronization]({{< relref "#configure-team-synchronization" >}}).
|
||||||
@@ -113,6 +115,7 @@ The following table outlines the various generic OAuth2 configuration options. Y
|
|||||||
| `tls_client_key` | No | The path to the key. | |
|
| `tls_client_key` | No | The path to the key. | |
|
||||||
| `tls_client_ca` | No | The path to the trusted certificate authority list. | |
|
| `tls_client_ca` | No | The path to the trusted certificate authority list. | |
|
||||||
| `use_pkce` | No | Set to `true` to use [Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636). Grafana uses the SHA256 based `S256` challenge method and a 128 bytes (base64url encoded) code verifier. | `false` |
|
| `use_pkce` | No | Set to `true` to use [Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636). Grafana uses the SHA256 based `S256` challenge method and a 128 bytes (base64url encoded) code verifier. | `false` |
|
||||||
|
| `use_refresh_token` | No | Set to `true` to use refresh token and check access token expiration. The `accessTokenExpirationCheck` feature toggle should also be enabled to use refresh token. | `false` |
|
||||||
|
|
||||||
### Configure login
|
### Configure login
|
||||||
|
|
||||||
@@ -169,11 +172,13 @@ When a user logs in using an OAuth2 provider, Grafana verifies that the access t
|
|||||||
|
|
||||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||||
|
|
||||||
To configure generic OAuth2 to use a refresh token, perform one or both of the following steps, if required:
|
To configure generic OAuth2 to use a refresh token, set `use_refresh_token` configuration option to `true` and perform one or both of the following steps, if required:
|
||||||
|
|
||||||
1. Extend the `scopes` field of `[auth.generic_oauth]` section in Grafana configuration file with additional scopes.
|
1. Extend the `scopes` field of `[auth.generic_oauth]` section in Grafana configuration file with additional scopes.
|
||||||
1. Enable the refresh token on the provider.
|
1. Enable the refresh token on the provider.
|
||||||
|
|
||||||
|
> **Note:** The `accessTokenExpirationCheck` feature toggle will be removed in Grafana v10.2.0 and the `use_refresh_token` configuration value will be used instead for configuring refresh token fetching and access token expiration check.
|
||||||
|
|
||||||
## Configure role mapping
|
## Configure role mapping
|
||||||
|
|
||||||
Unless `skip_org_role_sync` option is enabled, the user's role will be set to the role retrieved from the auth provider upon user login.
|
Unless `skip_org_role_sync` option is enabled, the user's role will be set to the role retrieved from the auth provider upon user login.
|
||||||
@@ -336,6 +341,7 @@ To set up generic OAuth2 authentication with Auth0, follow these steps:
|
|||||||
token_url = https://<domain>/oauth/token
|
token_url = https://<domain>/oauth/token
|
||||||
api_url = https://<domain>/userinfo
|
api_url = https://<domain>/userinfo
|
||||||
use_pkce = true
|
use_pkce = true
|
||||||
|
use_refresh_token = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Set up OAuth2 with Bitbucket
|
### Set up OAuth2 with Bitbucket
|
||||||
@@ -368,6 +374,7 @@ To set up generic OAuth2 authentication with Bitbucket, follow these steps:
|
|||||||
team_ids_attribute_path = values[*].workspace.slug
|
team_ids_attribute_path = values[*].workspace.slug
|
||||||
team_ids =
|
team_ids =
|
||||||
allowed_organizations =
|
allowed_organizations =
|
||||||
|
use_refresh_token = true
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, a refresh token is included in the response for the **Authorization Code Grant**.
|
By default, a refresh token is included in the response for the **Authorization Code Grant**.
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ Grafana uses a refresh token to obtain a new access token without requiring the
|
|||||||
|
|
||||||
By default, GitLab provides a refresh token.
|
By default, GitLab provides a refresh token.
|
||||||
|
|
||||||
|
Refresh token fetching and access token expiration check is enabled by default for the GitLab provider since Grafana v10.1.0 if the `accessTokenExpirationCheck` feature toggle is enabled. If you would like to disable access token expiration check then set the `use_refresh_token` configuration value to `false`.
|
||||||
|
|
||||||
|
> **Note:** The `accessTokenExpirationCheck` feature toggle will be removed in Grafana v10.2.0 and the `use_refresh_token` configuration value will be used instead for configuring refresh token fetching and access token expiration check.
|
||||||
|
|
||||||
### allowed_groups
|
### allowed_groups
|
||||||
|
|
||||||
To limit access to authenticated users that are members of one or more [GitLab
|
To limit access to authenticated users that are members of one or more [GitLab
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ Grafana uses a refresh token to obtain a new access token without requiring the
|
|||||||
|
|
||||||
By default, Grafana includes the `access_type=offline` parameter in the authorization request to request a refresh token.
|
By default, Grafana includes the `access_type=offline` parameter in the authorization request to request a refresh token.
|
||||||
|
|
||||||
|
Refresh token fetching and access token expiration check is enabled by default for the Google provider since Grafana v10.1.0 if the `accessTokenExpirationCheck` feature toggle is enabled. If you would like to disable access token expiration check then set the `use_refresh_token` configuration value to `false`.
|
||||||
|
|
||||||
|
> **Note:** The `accessTokenExpirationCheck` feature toggle will be removed in Grafana v10.2.0 and the `use_refresh_token` configuration value will be used instead for configuring refresh token fetching and access token expiration check.
|
||||||
|
|
||||||
### Configure automatic login
|
### Configure automatic login
|
||||||
|
|
||||||
Set `auto_login` option to true to attempt login automatically, skipping the login screen.
|
Set `auto_login` option to true to attempt login automatically, skipping the login screen.
|
||||||
|
|||||||
+4
-1
@@ -75,7 +75,10 @@ When a user logs in using an OAuth provider, Grafana verifies that the access to
|
|||||||
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
Grafana uses a refresh token to obtain a new access token without requiring the user to log in again. If a refresh token doesn't exist, Grafana logs the user out of the system after the access token has expired.
|
||||||
|
|
||||||
1. To enable the `Refresh Token`, grant type in the `General Settings` section.
|
1. To enable the `Refresh Token`, grant type in the `General Settings` section.
|
||||||
1. Extend the `scopes` in `[auth.okta]` with `offline_access`.
|
1. Extend the `scopes` in `[auth.okta]` with `offline_access` for Grafana versions between v9.3 and v10.0.x.
|
||||||
|
1. Set `use_refresh_token` in `[auth.okta]` to `true` for Grafana versions v10.1.0 and later.
|
||||||
|
|
||||||
|
> **Note:** The `accessTokenExpirationCheck` feature toggle will be removed in Grafana v10.2.0 and the `use_refresh_token` configuration value will be used instead for configuring refresh token fetching and access token expiration check.
|
||||||
|
|
||||||
### Configure allowed groups and domains
|
### Configure allowed groups and domains
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SocialGenericOAuth struct {
|
type SocialGenericOAuth struct {
|
||||||
@@ -489,13 +487,6 @@ func (s *SocialGenericOAuth) FetchOrganizations(ctx context.Context, client *htt
|
|||||||
return logins, true
|
return logins, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGenericOAuth) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
|
||||||
if s.features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) {
|
|
||||||
opts = append(opts, oauth2.AccessTypeOffline)
|
|
||||||
}
|
|
||||||
return s.SocialBase.AuthCodeURL(state, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocialGenericOAuth) SupportBundleContent(bf *bytes.Buffer) error {
|
func (s *SocialGenericOAuth) SupportBundleContent(bf *bytes.Buffer) error {
|
||||||
bf.WriteString("## GenericOAuth specific configuration\n\n")
|
bf.WriteString("## GenericOAuth specific configuration\n\n")
|
||||||
bf.WriteString("```ini\n")
|
bf.WriteString("```ini\n")
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func (s *SocialGoogle) extractFromAPI(ctx context.Context, client *http.Client)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGoogle) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
func (s *SocialGoogle) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
||||||
if s.features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) {
|
if s.features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) && s.useRefreshToken {
|
||||||
opts = append(opts, oauth2.AccessTypeOffline, oauth2.ApprovalForce)
|
opts = append(opts, oauth2.AccessTypeOffline, oauth2.ApprovalForce)
|
||||||
}
|
}
|
||||||
return s.SocialBase.AuthCodeURL(state, opts...)
|
return s.SocialBase.AuthCodeURL(state, opts...)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
@@ -31,6 +32,10 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OfflineAccessScope = "offline_access"
|
||||||
|
)
|
||||||
|
|
||||||
type SocialService struct {
|
type SocialService struct {
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
|
|
||||||
@@ -66,6 +71,7 @@ type OAuthInfo struct {
|
|||||||
RoleAttributeStrict bool `toml:"role_attribute_strict"`
|
RoleAttributeStrict bool `toml:"role_attribute_strict"`
|
||||||
TlsSkipVerify bool `toml:"tls_skip_verify"`
|
TlsSkipVerify bool `toml:"tls_skip_verify"`
|
||||||
UsePKCE bool `toml:"use_pkce"`
|
UsePKCE bool `toml:"use_pkce"`
|
||||||
|
UseRefreshToken bool `toml:"use_refresh_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(cfg *setting.Cfg,
|
func ProvideService(cfg *setting.Cfg,
|
||||||
@@ -111,6 +117,7 @@ func ProvideService(cfg *setting.Cfg,
|
|||||||
TlsClientCa: sec.Key("tls_client_ca").String(),
|
TlsClientCa: sec.Key("tls_client_ca").String(),
|
||||||
TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(),
|
TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(),
|
||||||
UsePKCE: sec.Key("use_pkce").MustBool(),
|
UsePKCE: sec.Key("use_pkce").MustBool(),
|
||||||
|
UseRefreshToken: sec.Key("use_refresh_token").MustBool(false),
|
||||||
AllowAssignGrafanaAdmin: sec.Key("allow_assign_grafana_admin").MustBool(false),
|
AllowAssignGrafanaAdmin: sec.Key("allow_assign_grafana_admin").MustBool(false),
|
||||||
AutoLogin: sec.Key("auto_login").MustBool(false),
|
AutoLogin: sec.Key("auto_login").MustBool(false),
|
||||||
}
|
}
|
||||||
@@ -198,6 +205,9 @@ func ProvideService(cfg *setting.Cfg,
|
|||||||
forceUseGraphAPI: sec.Key("force_use_graph_api").MustBool(false),
|
forceUseGraphAPI: sec.Key("force_use_graph_api").MustBool(false),
|
||||||
skipOrgRoleSync: cfg.AzureADSkipOrgRoleSync,
|
skipOrgRoleSync: cfg.AzureADSkipOrgRoleSync,
|
||||||
}
|
}
|
||||||
|
if info.UseRefreshToken && features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) {
|
||||||
|
appendUniqueScope(&config, OfflineAccessScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Okta
|
// Okta
|
||||||
@@ -208,6 +218,9 @@ func ProvideService(cfg *setting.Cfg,
|
|||||||
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
||||||
skipOrgRoleSync: cfg.OktaSkipOrgRoleSync,
|
skipOrgRoleSync: cfg.OktaSkipOrgRoleSync,
|
||||||
}
|
}
|
||||||
|
if info.UseRefreshToken && features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) {
|
||||||
|
appendUniqueScope(&config, OfflineAccessScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic - Uses the same scheme as GitHub.
|
// Generic - Uses the same scheme as GitHub.
|
||||||
@@ -271,6 +284,7 @@ func (b *BasicUserInfo) String() string {
|
|||||||
b.Id, b.Name, b.Email, b.Login, b.Role, b.Groups)
|
b.Id, b.Name, b.Email, b.Login, b.Role, b.Groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:generate mockery --name SocialConnector --structname MockSocialConnector --outpkg socialtest --filename social_connector_mock.go --output ../socialtest/
|
||||||
type SocialConnector interface {
|
type SocialConnector interface {
|
||||||
UserInfo(ctx context.Context, client *http.Client, token *oauth2.Token) (*BasicUserInfo, error)
|
UserInfo(ctx context.Context, client *http.Client, token *oauth2.Token) (*BasicUserInfo, error)
|
||||||
IsEmailAllowed(email string) bool
|
IsEmailAllowed(email string) bool
|
||||||
@@ -295,6 +309,7 @@ type SocialBase struct {
|
|||||||
autoAssignOrgRole string
|
autoAssignOrgRole string
|
||||||
skipOrgRoleSync bool
|
skipOrgRoleSync bool
|
||||||
features featuremgmt.FeatureManager
|
features featuremgmt.FeatureManager
|
||||||
|
useRefreshToken bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
@@ -344,6 +359,7 @@ func newSocialBase(name string,
|
|||||||
roleAttributeStrict: info.RoleAttributeStrict,
|
roleAttributeStrict: info.RoleAttributeStrict,
|
||||||
skipOrgRoleSync: skipOrgRoleSync,
|
skipOrgRoleSync: skipOrgRoleSync,
|
||||||
features: features,
|
features: features,
|
||||||
|
useRefreshToken: info.UseRefreshToken,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,3 +608,9 @@ func (s *SocialBase) retrieveRawIDToken(idToken interface{}) ([]byte, error) {
|
|||||||
|
|
||||||
return rawJSON, nil
|
return rawJSON, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendUniqueScope(config *oauth2.Config, scope string) {
|
||||||
|
if !slices.Contains(config.Scopes, OfflineAccessScope) {
|
||||||
|
config.Scopes = append(config.Scopes, OfflineAccessScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
// Code generated by mockery v2.27.1. DO NOT EDIT.
|
||||||
|
|
||||||
|
package socialtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
bytes "bytes"
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
http "net/http"
|
||||||
|
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
oauth2 "golang.org/x/oauth2"
|
||||||
|
|
||||||
|
social "github.com/grafana/grafana/pkg/login/social"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockSocialConnector is an autogenerated mock type for the SocialConnector type
|
||||||
|
type MockSocialConnector struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthCodeURL provides a mock function with given fields: state, opts
|
||||||
|
func (_m *MockSocialConnector) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
||||||
|
_va := make([]interface{}, len(opts))
|
||||||
|
for _i := range opts {
|
||||||
|
_va[_i] = opts[_i]
|
||||||
|
}
|
||||||
|
var _ca []interface{}
|
||||||
|
_ca = append(_ca, state)
|
||||||
|
_ca = append(_ca, _va...)
|
||||||
|
ret := _m.Called(_ca...)
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
if rf, ok := ret.Get(0).(func(string, ...oauth2.AuthCodeOption) string); ok {
|
||||||
|
r0 = rf(state, opts...)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client provides a mock function with given fields: ctx, t
|
||||||
|
func (_m *MockSocialConnector) Client(ctx context.Context, t *oauth2.Token) *http.Client {
|
||||||
|
ret := _m.Called(ctx, t)
|
||||||
|
|
||||||
|
var r0 *http.Client
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *oauth2.Token) *http.Client); ok {
|
||||||
|
r0 = rf(ctx, t)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*http.Client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange provides a mock function with given fields: ctx, code, authOptions
|
||||||
|
func (_m *MockSocialConnector) Exchange(ctx context.Context, code string, authOptions ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
|
||||||
|
_va := make([]interface{}, len(authOptions))
|
||||||
|
for _i := range authOptions {
|
||||||
|
_va[_i] = authOptions[_i]
|
||||||
|
}
|
||||||
|
var _ca []interface{}
|
||||||
|
_ca = append(_ca, ctx, code)
|
||||||
|
_ca = append(_ca, _va...)
|
||||||
|
ret := _m.Called(_ca...)
|
||||||
|
|
||||||
|
var r0 *oauth2.Token
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string, ...oauth2.AuthCodeOption) (*oauth2.Token, error)); ok {
|
||||||
|
return rf(ctx, code, authOptions...)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string, ...oauth2.AuthCodeOption) *oauth2.Token); ok {
|
||||||
|
r0 = rf(ctx, code, authOptions...)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*oauth2.Token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, string, ...oauth2.AuthCodeOption) error); ok {
|
||||||
|
r1 = rf(ctx, code, authOptions...)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmailAllowed provides a mock function with given fields: email
|
||||||
|
func (_m *MockSocialConnector) IsEmailAllowed(email string) bool {
|
||||||
|
ret := _m.Called(email)
|
||||||
|
|
||||||
|
var r0 bool
|
||||||
|
if rf, ok := ret.Get(0).(func(string) bool); ok {
|
||||||
|
r0 = rf(email)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSignupAllowed provides a mock function with given fields:
|
||||||
|
func (_m *MockSocialConnector) IsSignupAllowed() bool {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 bool
|
||||||
|
if rf, ok := ret.Get(0).(func() bool); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportBundleContent provides a mock function with given fields: _a0
|
||||||
|
func (_m *MockSocialConnector) SupportBundleContent(_a0 *bytes.Buffer) error {
|
||||||
|
ret := _m.Called(_a0)
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(*bytes.Buffer) error); ok {
|
||||||
|
r0 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource provides a mock function with given fields: ctx, t
|
||||||
|
func (_m *MockSocialConnector) TokenSource(ctx context.Context, t *oauth2.Token) oauth2.TokenSource {
|
||||||
|
ret := _m.Called(ctx, t)
|
||||||
|
|
||||||
|
var r0 oauth2.TokenSource
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *oauth2.Token) oauth2.TokenSource); ok {
|
||||||
|
r0 = rf(ctx, t)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(oauth2.TokenSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo provides a mock function with given fields: ctx, client, token
|
||||||
|
func (_m *MockSocialConnector) UserInfo(ctx context.Context, client *http.Client, token *oauth2.Token) (*social.BasicUserInfo, error) {
|
||||||
|
ret := _m.Called(ctx, client, token)
|
||||||
|
|
||||||
|
var r0 *social.BasicUserInfo
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *http.Client, *oauth2.Token) (*social.BasicUserInfo, error)); ok {
|
||||||
|
return rf(ctx, client, token)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *http.Client, *oauth2.Token) *social.BasicUserInfo); ok {
|
||||||
|
r0 = rf(ctx, client, token)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*social.BasicUserInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, *http.Client, *oauth2.Token) error); ok {
|
||||||
|
r1 = rf(ctx, client, token)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockConstructorTestingTNewMockSocialConnector interface {
|
||||||
|
mock.TestingT
|
||||||
|
Cleanup(func())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockSocialConnector creates a new instance of MockSocialConnector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewMockSocialConnector(t mockConstructorTestingTNewMockSocialConnector) *MockSocialConnector {
|
||||||
|
mock := &MockSocialConnector{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package socialtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/login/social"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeSocialService struct {
|
||||||
|
ExpectedAuthInfoProvider *social.OAuthInfo
|
||||||
|
ExpectedConnector social.SocialConnector
|
||||||
|
ExpectedHttpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fss *FakeSocialService) GetOAuthProviders() map[string]bool {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fss *FakeSocialService) GetOAuthHttpClient(string) (*http.Client, error) {
|
||||||
|
return fss.ExpectedHttpClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fss *FakeSocialService) GetConnector(string) (social.SocialConnector, error) {
|
||||||
|
return fss.ExpectedConnector, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fss *FakeSocialService) GetOAuthInfoProvider(string) *social.OAuthInfo {
|
||||||
|
return fss.ExpectedAuthInfoProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fss *FakeSocialService) GetOAuthInfoProviders() map[string]*social.OAuthInfo {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
@@ -158,7 +158,7 @@ func ProvideService(
|
|||||||
s.RegisterPostAuthHook(userSyncService.SyncLastSeenHook, 40)
|
s.RegisterPostAuthHook(userSyncService.SyncLastSeenHook, 40)
|
||||||
|
|
||||||
if features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) {
|
if features.IsEnabled(featuremgmt.FlagAccessTokenExpirationCheck) {
|
||||||
s.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService).SyncOauthTokenHook, 60)
|
s.RegisterPostAuthHook(sync.ProvideOAuthTokenSync(oauthTokenService, sessionService, socialService).SyncOauthTokenHook, 60)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.RegisterPostAuthHook(userSyncService.FetchSyncedUserHook, 100)
|
s.RegisterPostAuthHook(userSyncService.FetchSyncedUserHook, 100)
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package sync
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/login/social"
|
||||||
"github.com/grafana/grafana/pkg/services/auth"
|
"github.com/grafana/grafana/pkg/services/auth"
|
||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||||
@@ -15,15 +17,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errExpiredAccessToken = errutil.NewBase(errutil.StatusUnauthorized, "oauth.expired-token")
|
errExpiredAccessToken = errutil.NewBase(
|
||||||
|
errutil.StatusUnauthorized,
|
||||||
|
"oauth.expired-token",
|
||||||
|
errutil.WithPublicMessage("OAuth access token expired"))
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService auth.UserTokenService) *OAuthTokenSync {
|
func ProvideOAuthTokenSync(service oauthtoken.OAuthTokenService, sessionService auth.UserTokenService, socialService social.Service) *OAuthTokenSync {
|
||||||
return &OAuthTokenSync{
|
return &OAuthTokenSync{
|
||||||
log.New("oauth_token.sync"),
|
log.New("oauth_token.sync"),
|
||||||
localcache.New(maxOAuthTokenCacheTTL, 15*time.Minute),
|
localcache.New(maxOAuthTokenCacheTTL, 15*time.Minute),
|
||||||
service,
|
service,
|
||||||
sessionService,
|
sessionService,
|
||||||
|
socialService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +38,7 @@ type OAuthTokenSync struct {
|
|||||||
cache *localcache.CacheService
|
cache *localcache.CacheService
|
||||||
service oauthtoken.OAuthTokenService
|
service oauthtoken.OAuthTokenService
|
||||||
sessionService auth.UserTokenService
|
sessionService auth.UserTokenService
|
||||||
|
socialService social.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
|
func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn.Identity, _ *authn.Request) error {
|
||||||
@@ -64,6 +71,19 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the token's auth provider (f.e. azuread)
|
||||||
|
provider := strings.TrimPrefix(token.AuthModule, "oauth_")
|
||||||
|
currentOAuthInfo := s.socialService.GetOAuthInfoProvider(provider)
|
||||||
|
if currentOAuthInfo == nil {
|
||||||
|
s.log.Warn("OAuth provider not found", "provider", provider)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if refresh token handling is disabled for this provider, we can skip the hook
|
||||||
|
if !currentOAuthInfo.UseRefreshToken {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
expires := token.OAuthExpiry.Round(0).Add(-oauthtoken.ExpiryDelta)
|
expires := token.OAuthExpiry.Round(0).Add(-oauthtoken.ExpiryDelta)
|
||||||
// token has not expired, so we don't have to refresh it
|
// token has not expired, so we don't have to refresh it
|
||||||
if !expires.Before(time.Now()) {
|
if !expires.Before(time.Now()) {
|
||||||
@@ -78,14 +98,14 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, identity *authn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.service.InvalidateOAuthTokens(ctx, token); err != nil {
|
if err := s.service.InvalidateOAuthTokens(ctx, token); err != nil {
|
||||||
s.log.FromContext(ctx).Error("Failed invalidate OAuth tokens", "id", identity.ID, "error", err)
|
s.log.FromContext(ctx).Error("Failed to invalidate OAuth tokens", "id", identity.ID, "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.sessionService.RevokeToken(ctx, identity.SessionToken, false); err != nil {
|
if err := s.sessionService.RevokeToken(ctx, identity.SessionToken, false); err != nil {
|
||||||
s.log.FromContext(ctx).Error("Failed to revoke session token", "id", identity.ID, "tokenId", identity.SessionToken.Id, "error", err)
|
s.log.FromContext(ctx).Error("Failed to revoke session token", "id", identity.ID, "tokenId", identity.SessionToken.Id, "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errExpiredAccessToken.Errorf("oauth access token could not be refreshed: %w", auth.ErrInvalidSessionToken)
|
return errExpiredAccessToken.Errorf("oauth access token could not be refreshed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/login/social"
|
||||||
|
"github.com/grafana/grafana/pkg/login/socialtest"
|
||||||
"github.com/grafana/grafana/pkg/services/auth"
|
"github.com/grafana/grafana/pkg/services/auth"
|
||||||
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
@@ -20,8 +22,9 @@ import (
|
|||||||
|
|
||||||
func TestOauthTokenSync_SyncOAuthTokenHook(t *testing.T) {
|
func TestOauthTokenSync_SyncOAuthTokenHook(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
desc string
|
desc string
|
||||||
identity *authn.Identity
|
identity *authn.Identity
|
||||||
|
oauthInfo *social.OAuthInfo
|
||||||
|
|
||||||
expectedHasEntryToken *login.UserAuth
|
expectedHasEntryToken *login.UserAuth
|
||||||
expectHasEntryCalled bool
|
expectHasEntryCalled bool
|
||||||
@@ -84,6 +87,13 @@ func TestOauthTokenSync_SyncOAuthTokenHook(t *testing.T) {
|
|||||||
expectRevokeTokenCalled: true,
|
expectRevokeTokenCalled: true,
|
||||||
expectedHasEntryToken: &login.UserAuth{OAuthExpiry: time.Now().Add(-10 * time.Minute)},
|
expectedHasEntryToken: &login.UserAuth{OAuthExpiry: time.Now().Add(-10 * time.Minute)},
|
||||||
expectedErr: errExpiredAccessToken,
|
expectedErr: errExpiredAccessToken,
|
||||||
|
}, {
|
||||||
|
desc: "should skip sync when use_refresh_token is disabled",
|
||||||
|
identity: &authn.Identity{ID: "user:1", SessionToken: &auth.UserToken{}, AuthenticatedBy: login.GitLabAuthModule},
|
||||||
|
expectHasEntryCalled: true,
|
||||||
|
expectTryRefreshTokenCalled: false,
|
||||||
|
expectedHasEntryToken: &login.UserAuth{OAuthExpiry: time.Now().Add(-10 * time.Minute)},
|
||||||
|
oauthInfo: &social.OAuthInfo{UseRefreshToken: false},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,11 +128,22 @@ func TestOauthTokenSync_SyncOAuthTokenHook(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tt.oauthInfo == nil {
|
||||||
|
tt.oauthInfo = &social.OAuthInfo{
|
||||||
|
UseRefreshToken: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socialService := &socialtest.FakeSocialService{
|
||||||
|
ExpectedAuthInfoProvider: tt.oauthInfo,
|
||||||
|
}
|
||||||
|
|
||||||
sync := &OAuthTokenSync{
|
sync := &OAuthTokenSync{
|
||||||
log: log.NewNopLogger(),
|
log: log.NewNopLogger(),
|
||||||
cache: localcache.New(0, 0),
|
cache: localcache.New(0, 0),
|
||||||
service: service,
|
service: service,
|
||||||
sessionService: sessionService,
|
sessionService: sessionService,
|
||||||
|
socialService: socialService,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := sync.SyncOauthTokenHook(context.Background(), tt.identity, nil)
|
err := sync.SyncOauthTokenHook(context.Background(), tt.identity, nil)
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package oauthtoken
|
package oauthtoken
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,7 +13,7 @@ import (
|
|||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||||
"github.com/grafana/grafana/pkg/login/social"
|
"github.com/grafana/grafana/pkg/login/socialtest"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
@@ -221,12 +219,12 @@ func TestService_TryTokenRefresh_DifferentAuthModuleForUser(t *testing.T) {
|
|||||||
socialConnector.AssertNotCalled(t, "TokenSource")
|
socialConnector.AssertNotCalled(t, "TokenSource")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupOAuthTokenService(t *testing.T) (*Service, *FakeAuthInfoStore, *MockSocialConnector) {
|
func setupOAuthTokenService(t *testing.T) (*Service, *FakeAuthInfoStore, *socialtest.MockSocialConnector) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
socialConnector := &MockSocialConnector{}
|
socialConnector := &socialtest.MockSocialConnector{}
|
||||||
socialService := &FakeSocialService{
|
socialService := &socialtest.FakeSocialService{
|
||||||
connector: socialConnector,
|
ExpectedConnector: socialConnector,
|
||||||
}
|
}
|
||||||
|
|
||||||
authInfoStore := &FakeAuthInfoStore{}
|
authInfoStore := &FakeAuthInfoStore{}
|
||||||
@@ -239,74 +237,6 @@ func setupOAuthTokenService(t *testing.T) (*Service, *FakeAuthInfoStore, *MockSo
|
|||||||
}, authInfoStore, socialConnector
|
}, authInfoStore, socialConnector
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeSocialService struct {
|
|
||||||
httpClient *http.Client
|
|
||||||
connector *MockSocialConnector
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fss *FakeSocialService) GetOAuthProviders() map[string]bool {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fss *FakeSocialService) GetOAuthHttpClient(string) (*http.Client, error) {
|
|
||||||
return fss.httpClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fss *FakeSocialService) GetConnector(string) (social.SocialConnector, error) {
|
|
||||||
return fss.connector, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fss *FakeSocialService) GetOAuthInfoProvider(string) *social.OAuthInfo {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fss *FakeSocialService) GetOAuthInfoProviders() map[string]*social.OAuthInfo {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockSocialConnector struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) Type() int {
|
|
||||||
args := m.Called()
|
|
||||||
return args.Int(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) UserInfo(ctx context.Context, client *http.Client, token *oauth2.Token) (*social.BasicUserInfo, error) {
|
|
||||||
args := m.Called(client, token)
|
|
||||||
return args.Get(0).(*social.BasicUserInfo), args.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) IsEmailAllowed(email string) bool {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) IsSignupAllowed() bool {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) Exchange(ctx context.Context, code string, authOptions ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) Client(ctx context.Context, t *oauth2.Token) *http.Client {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) TokenSource(ctx context.Context, t *oauth2.Token) oauth2.TokenSource {
|
|
||||||
args := m.Called(ctx, t)
|
|
||||||
return args.Get(0).(oauth2.TokenSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockSocialConnector) SupportBundleContent(bf *bytes.Buffer) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FakeAuthInfoStore struct {
|
type FakeAuthInfoStore struct {
|
||||||
login.Store
|
login.Store
|
||||||
ExpectedError error
|
ExpectedError error
|
||||||
|
|||||||
Reference in New Issue
Block a user