Files
grafana/pkg/services/login/authinfo.go
T
6abe99efd6 Auth: Passwordless Login Option Using Magic Links (#95436)
* initial passwordless client

* passwordless login page

* Working basic e2e flow

* Add todo comments

* Improve the passwordless login flow

* improved passwordless login, backend for passwordless signup

* add expiration to emails

* update email templates & render username & name fields on signup

* improve email templates

* change login page text while awaiting passwordless code

* fix merge conflicts

* use claims.TypeUser

* add initial passwordless tests

* better error messages

* simplified error name

* remove completed TODOs

* linting & minor test improvements & rename passwordless routes

* more linting fixes

* move code generation to its own func, use locationService to get query params

* fix ampersand in email templates & use passwordless api routes in LoginCtrl

* txt emails more closely match html email copy

* move passwordless auth behind experimental feature toggle

* fix PasswordlessLogin property failing typecheck

* make update-workspace

* user correct placeholder

* Update emails/templates/passwordless_verify_existing_user.txt

Co-authored-by: Dan Cech <dcech@grafana.com>

* Update emails/templates/passwordless_verify_existing_user.mjml

Co-authored-by: Dan Cech <dcech@grafana.com>

* Update emails/templates/passwordless_verify_new_user.txt

Co-authored-by: Dan Cech <dcech@grafana.com>

* Update emails/templates/passwordless_verify_new_user.txt

Co-authored-by: Dan Cech <dcech@grafana.com>

* Update emails/templates/passwordless_verify_new_user.mjml

Co-authored-by: Dan Cech <dcech@grafana.com>

* use &amp; in email templates

* Update emails/templates/passwordless_verify_existing_user.txt

Co-authored-by: Dan Cech <dcech@grafana.com>

* remove IP address validation

* struct for passwordless settings

* revert go.work.sum changes

* mock locationService.getSearch in failing test

---------

Co-authored-by: Mihaly Gyongyosi <mgyongyosi@users.noreply.github.com>
Co-authored-by: Dan Cech <dcech@grafana.com>
2024-11-14 08:50:55 -05:00

158 lines
5.1 KiB
Go

package login
import (
"context"
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/setting"
)
type AuthInfoService interface {
GetAuthInfo(ctx context.Context, query *GetAuthInfoQuery) (*UserAuth, error)
GetUserLabels(ctx context.Context, query GetUserLabelsQuery) (map[int64]string, error)
SetAuthInfo(ctx context.Context, cmd *SetAuthInfoCommand) error
UpdateAuthInfo(ctx context.Context, cmd *UpdateAuthInfoCommand) error
DeleteUserAuthInfo(ctx context.Context, userID int64) error
}
type Store interface {
GetAuthInfo(ctx context.Context, query *GetAuthInfoQuery) (*UserAuth, error)
GetUserLabels(ctx context.Context, query GetUserLabelsQuery) (map[int64]string, error)
SetAuthInfo(ctx context.Context, cmd *SetAuthInfoCommand) error
UpdateAuthInfo(ctx context.Context, cmd *UpdateAuthInfoCommand) error
DeleteUserAuthInfo(ctx context.Context, userID int64) error
}
const (
// modules
PasswordAuthModule = "password"
PasswordlessAuthModule = "passwordless"
APIKeyAuthModule = "apikey"
SAMLAuthModule = "auth.saml"
LDAPAuthModule = "ldap"
AuthProxyAuthModule = "authproxy"
JWTModule = "jwt"
ExtendedJWTModule = "extendedjwt"
RenderModule = "render"
// OAuth provider modules
AzureADAuthModule = "oauth_azuread"
GoogleAuthModule = "oauth_google"
GitLabAuthModule = "oauth_gitlab"
GithubAuthModule = "oauth_github"
GenericOAuthModule = "oauth_generic_oauth"
GrafanaComAuthModule = "oauth_grafana_com"
GrafanaNetAuthModule = "oauth_grafananet"
OktaAuthModule = "oauth_okta"
// labels
SAMLLabel = "SAML"
LDAPLabel = "LDAP"
JWTLabel = "JWT"
// OAuth provider labels
AuthProxyLabel = "Auth Proxy"
AzureADLabel = "AzureAD"
GoogleLabel = "Google"
GenericOAuthLabel = "Generic OAuth"
GitLabLabel = "GitLab"
GithubLabel = "GitHub"
GrafanaComLabel = "grafana.com"
OktaLabel = "Okta"
)
// IsExternnalySynced is used to tell if the user roles are externally synced
// true means that the org role sync is handled by Grafana
// Note: currently the users authinfo is overridden each time the user logs in
// https://github.com/grafana/grafana/blob/4181acec72f76df7ad02badce13769bae4a1f840/pkg/services/login/authinfoservice/database/database.go#L61
// this means that if the user has multiple auth providers and one of them is set to sync org roles
// then IsExternallySynced will be true for this one provider and false for the others
func IsExternallySynced(cfg *setting.Cfg, authModule string, oauthInfo *social.OAuthInfo) bool {
// provider enabled in config
if !IsProviderEnabled(cfg, authModule, oauthInfo) {
return false
}
// first check SAML, LDAP and JWT
switch authModule {
case SAMLAuthModule:
return !cfg.SAMLSkipOrgRoleSync
case LDAPAuthModule:
return !cfg.LDAPSkipOrgRoleSync
case JWTModule:
return !cfg.JWTAuth.SkipOrgRoleSync
}
switch authModule {
case GoogleAuthModule, OktaAuthModule, AzureADAuthModule, GitLabAuthModule, GithubAuthModule, GrafanaComAuthModule, GenericOAuthModule:
if oauthInfo == nil {
return false
}
return !oauthInfo.SkipOrgRoleSync
}
return true
}
// IsGrafanaAdminExternallySynced returns true if Grafana server admin role is being managed by an external auth provider, and false otherwise.
// Grafana admin role sync is available for JWT, OAuth providers and LDAP.
// For JWT and OAuth providers there is an additional config option `allow_assign_grafana_admin` that has to be enabled for Grafana Admin role to be synced.
func IsGrafanaAdminExternallySynced(cfg *setting.Cfg, oauthInfo *social.OAuthInfo, authModule string) bool {
if !IsExternallySynced(cfg, authModule, oauthInfo) {
return false
}
switch authModule {
case JWTModule:
return cfg.JWTAuth.AllowAssignGrafanaAdmin
case SAMLAuthModule:
return cfg.SAMLRoleValuesGrafanaAdmin != ""
case LDAPAuthModule:
return true
default:
return oauthInfo != nil && oauthInfo.AllowAssignGrafanaAdmin
}
}
func IsProviderEnabled(cfg *setting.Cfg, authModule string, oauthInfo *social.OAuthInfo) bool {
switch authModule {
case SAMLAuthModule:
return cfg.SAMLAuthEnabled
case LDAPAuthModule:
return cfg.LDAPAuthEnabled
case JWTModule:
return cfg.JWTAuth.Enabled
case GoogleAuthModule, OktaAuthModule, AzureADAuthModule, GitLabAuthModule, GithubAuthModule, GrafanaComAuthModule, GenericOAuthModule:
if oauthInfo == nil {
return false
}
return oauthInfo.Enabled
}
return false
}
// used for frontend to display a more user friendly label
func GetAuthProviderLabel(authModule string) string {
switch authModule {
case GithubAuthModule:
return GithubLabel
case GoogleAuthModule:
return GoogleLabel
case AzureADAuthModule:
return AzureADLabel
case GitLabAuthModule:
return GitLabLabel
case OktaAuthModule:
return OktaLabel
case GrafanaComAuthModule, GrafanaNetAuthModule:
return GrafanaComLabel
case SAMLAuthModule:
return SAMLLabel
case LDAPAuthModule, "": // FIXME: verify this situation doesn't exist anymore
return LDAPLabel
case JWTModule:
return JWTLabel
case AuthProxyAuthModule:
return AuthProxyLabel
case GenericOAuthModule:
return GenericOAuthLabel
default:
return "Unknown"
}
}