mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Add microsoft oauth2 providers (#16544)
* Clean up oauth2 providers Signed-off-by: Andrew Thornton <art27@cantab.net> * Add AzureAD, AzureADv2, MicrosoftOnline OAuth2 providers Signed-off-by: Andrew Thornton <art27@cantab.net> * Apply suggestions from code review * remove unused Scopes Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		
							
								
								
									
										1
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.sum
									
									
									
									
									
								
							| @@ -762,6 +762,7 @@ github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 | ||||
| github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | ||||
| github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= | ||||
| github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | ||||
| github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0= | ||||
| github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= | ||||
| github.com/markbates/goth v1.68.0 h1:90sKvjRAKHcl9V2uC9x/PJXeD78cFPiBsyP1xVhoQfA= | ||||
| github.com/markbates/goth v1.68.0/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw= | ||||
|   | ||||
| @@ -2441,6 +2441,7 @@ auths.oauth2_tokenURL = Token URL | ||||
| auths.oauth2_authURL = Authorize URL | ||||
| auths.oauth2_profileURL = Profile URL | ||||
| auths.oauth2_emailURL = Email URL | ||||
| auths.oauth2_tenant = Tenant | ||||
| auths.enable_auto_register = Enable Auto Registration | ||||
| auths.sspi_auto_create_users = Automatically create users | ||||
| auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								public/img/auth/azuread.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/auth/azuread.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/auth/azureadv2.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/auth/azureadv2.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 3.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/auth/microsoftonline.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/auth/microsoftonline.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 792 B | 
| @@ -98,8 +98,8 @@ func NewAuthSource(ctx *context.Context) { | ||||
| 	ctx.Data["AuthSources"] = authSources | ||||
| 	ctx.Data["SecurityProtocols"] = securityProtocols | ||||
| 	ctx.Data["SMTPAuths"] = smtp.Authenticators | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2.Providers | ||||
| 	ctx.Data["OAuth2DefaultCustomURLMappings"] = oauth2.DefaultCustomURLMappings | ||||
| 	oauth2providers := oauth2.GetOAuth2Providers() | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2providers | ||||
|  | ||||
| 	ctx.Data["SSPIAutoCreateUsers"] = true | ||||
| 	ctx.Data["SSPIAutoActivateUsers"] = true | ||||
| @@ -108,10 +108,7 @@ func NewAuthSource(ctx *context.Context) { | ||||
| 	ctx.Data["SSPIDefaultLanguage"] = "" | ||||
|  | ||||
| 	// only the first as default | ||||
| 	for key := range oauth2.Providers { | ||||
| 		ctx.Data["oauth2_provider"] = key | ||||
| 		break | ||||
| 	} | ||||
| 	ctx.Data["oauth2_provider"] = oauth2providers[0] | ||||
|  | ||||
| 	ctx.HTML(http.StatusOK, tplAuthNew) | ||||
| } | ||||
| @@ -170,6 +167,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { | ||||
| 			AuthURL:    form.Oauth2AuthURL, | ||||
| 			ProfileURL: form.Oauth2ProfileURL, | ||||
| 			EmailURL:   form.Oauth2EmailURL, | ||||
| 			Tenant:     form.Oauth2Tenant, | ||||
| 		} | ||||
| 	} else { | ||||
| 		customURLMapping = nil | ||||
| @@ -220,8 +218,8 @@ func NewAuthSourcePost(ctx *context.Context) { | ||||
| 	ctx.Data["AuthSources"] = authSources | ||||
| 	ctx.Data["SecurityProtocols"] = securityProtocols | ||||
| 	ctx.Data["SMTPAuths"] = smtp.Authenticators | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2.Providers | ||||
| 	ctx.Data["OAuth2DefaultCustomURLMappings"] = oauth2.DefaultCustomURLMappings | ||||
| 	oauth2providers := oauth2.GetOAuth2Providers() | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2providers | ||||
|  | ||||
| 	ctx.Data["SSPIAutoCreateUsers"] = true | ||||
| 	ctx.Data["SSPIAutoActivateUsers"] = true | ||||
| @@ -299,8 +297,8 @@ func EditAuthSource(ctx *context.Context) { | ||||
|  | ||||
| 	ctx.Data["SecurityProtocols"] = securityProtocols | ||||
| 	ctx.Data["SMTPAuths"] = smtp.Authenticators | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2.Providers | ||||
| 	ctx.Data["OAuth2DefaultCustomURLMappings"] = oauth2.DefaultCustomURLMappings | ||||
| 	oauth2providers := oauth2.GetOAuth2Providers() | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2providers | ||||
|  | ||||
| 	source, err := models.GetLoginSourceByID(ctx.ParamsInt64(":authid")) | ||||
| 	if err != nil { | ||||
| @@ -311,7 +309,17 @@ func EditAuthSource(ctx *context.Context) { | ||||
| 	ctx.Data["HasTLS"] = source.HasTLS() | ||||
|  | ||||
| 	if source.IsOAuth2() { | ||||
| 		ctx.Data["CurrentOAuth2Provider"] = oauth2.Providers[source.Cfg.(*oauth2.Source).Provider] | ||||
| 		type Named interface { | ||||
| 			Name() string | ||||
| 		} | ||||
|  | ||||
| 		for _, provider := range oauth2providers { | ||||
| 			if provider.Name() == source.Cfg.(Named).Name() { | ||||
| 				ctx.Data["CurrentOAuth2Provider"] = provider | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
| 	ctx.HTML(http.StatusOK, tplAuthEdit) | ||||
| } | ||||
| @@ -324,8 +332,8 @@ func EditAuthSourcePost(ctx *context.Context) { | ||||
| 	ctx.Data["PageIsAdminAuthentications"] = true | ||||
|  | ||||
| 	ctx.Data["SMTPAuths"] = smtp.Authenticators | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2.Providers | ||||
| 	ctx.Data["OAuth2DefaultCustomURLMappings"] = oauth2.DefaultCustomURLMappings | ||||
| 	oauth2providers := oauth2.GetOAuth2Providers() | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2providers | ||||
|  | ||||
| 	source, err := models.GetLoginSourceByID(ctx.ParamsInt64(":authid")) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -12,7 +12,6 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/base" | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/services/auth/source/oauth2" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -92,9 +91,19 @@ func loadSecurityData(ctx *context.Context) { | ||||
| 	for _, externalAccount := range accountLinks { | ||||
| 		if loginSource, err := models.GetLoginSourceByID(externalAccount.LoginSourceID); err == nil { | ||||
| 			var providerDisplayName string | ||||
| 			if loginSource.IsOAuth2() { | ||||
| 				providerTechnicalName := loginSource.Cfg.(*oauth2.Source).Provider | ||||
| 				providerDisplayName = oauth2.Providers[providerTechnicalName].DisplayName | ||||
|  | ||||
| 			type DisplayNamed interface { | ||||
| 				DisplayName() string | ||||
| 			} | ||||
|  | ||||
| 			type Named interface { | ||||
| 				Name() string | ||||
| 			} | ||||
|  | ||||
| 			if displayNamed, ok := loginSource.Cfg.(DisplayNamed); ok { | ||||
| 				providerDisplayName = displayNamed.DisplayName() | ||||
| 			} else if named, ok := loginSource.Cfg.(Named); ok { | ||||
| 				providerDisplayName = named.Name() | ||||
| 			} else { | ||||
| 				providerDisplayName = loginSource.Name | ||||
| 			} | ||||
|   | ||||
| @@ -13,80 +13,72 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| 	"github.com/markbates/goth/providers/bitbucket" | ||||
| 	"github.com/markbates/goth/providers/discord" | ||||
| 	"github.com/markbates/goth/providers/dropbox" | ||||
| 	"github.com/markbates/goth/providers/facebook" | ||||
| 	"github.com/markbates/goth/providers/gitea" | ||||
| 	"github.com/markbates/goth/providers/github" | ||||
| 	"github.com/markbates/goth/providers/gitlab" | ||||
| 	"github.com/markbates/goth/providers/google" | ||||
| 	"github.com/markbates/goth/providers/mastodon" | ||||
| 	"github.com/markbates/goth/providers/nextcloud" | ||||
| 	"github.com/markbates/goth/providers/openidConnect" | ||||
| 	"github.com/markbates/goth/providers/twitter" | ||||
| 	"github.com/markbates/goth/providers/yandex" | ||||
| ) | ||||
|  | ||||
| // Provider describes the display values of a single OAuth2 provider | ||||
| type Provider struct { | ||||
| 	Name             string | ||||
| 	DisplayName      string | ||||
| 	Image            string | ||||
| 	CustomURLMapping *CustomURLMapping | ||||
| // Provider is an interface for describing a single OAuth2 provider | ||||
| type Provider interface { | ||||
| 	Name() string | ||||
| 	DisplayName() string | ||||
| 	Image() string | ||||
| 	CustomURLSettings() *CustomURLSettings | ||||
| } | ||||
|  | ||||
| // GothProviderCreator provides a function to create a goth.Provider | ||||
| type GothProviderCreator interface { | ||||
| 	CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) | ||||
| } | ||||
|  | ||||
| // GothProvider is an interface for describing a single OAuth2 provider | ||||
| type GothProvider interface { | ||||
| 	Provider | ||||
| 	GothProviderCreator | ||||
| } | ||||
|  | ||||
| // ImagedProvider provide an overrided image setting for the provider | ||||
| type ImagedProvider struct { | ||||
| 	GothProvider | ||||
| 	image string | ||||
| } | ||||
|  | ||||
| // Image returns the image path for this provider | ||||
| func (i *ImagedProvider) Image() string { | ||||
| 	return i.image | ||||
| } | ||||
|  | ||||
| // NewImagedProvider is a constructor function for the ImagedProvider | ||||
| func NewImagedProvider(image string, provider GothProvider) *ImagedProvider { | ||||
| 	return &ImagedProvider{ | ||||
| 		GothProvider: provider, | ||||
| 		image:        image, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Providers contains the map of registered OAuth2 providers in Gitea (based on goth) | ||||
| // key is used to map the OAuth2Provider with the goth provider type (also in LoginSource.OAuth2Config.Provider) | ||||
| // value is used to store display data | ||||
| var Providers = map[string]Provider{ | ||||
| 	"bitbucket": {Name: "bitbucket", DisplayName: "Bitbucket", Image: "/assets/img/auth/bitbucket.png"}, | ||||
| 	"dropbox":   {Name: "dropbox", DisplayName: "Dropbox", Image: "/assets/img/auth/dropbox.png"}, | ||||
| 	"facebook":  {Name: "facebook", DisplayName: "Facebook", Image: "/assets/img/auth/facebook.png"}, | ||||
| 	"github": { | ||||
| 		Name: "github", DisplayName: "GitHub", Image: "/assets/img/auth/github.png", | ||||
| 		CustomURLMapping: &CustomURLMapping{ | ||||
| 			TokenURL:   github.TokenURL, | ||||
| 			AuthURL:    github.AuthURL, | ||||
| 			ProfileURL: github.ProfileURL, | ||||
| 			EmailURL:   github.EmailURL, | ||||
| 		}, | ||||
| 	}, | ||||
| 	"gitlab": { | ||||
| 		Name: "gitlab", DisplayName: "GitLab", Image: "/assets/img/auth/gitlab.png", | ||||
| 		CustomURLMapping: &CustomURLMapping{ | ||||
| 			TokenURL:   gitlab.TokenURL, | ||||
| 			AuthURL:    gitlab.AuthURL, | ||||
| 			ProfileURL: gitlab.ProfileURL, | ||||
| 		}, | ||||
| 	}, | ||||
| 	"gplus":         {Name: "gplus", DisplayName: "Google", Image: "/assets/img/auth/google.png"}, | ||||
| 	"openidConnect": {Name: "openidConnect", DisplayName: "OpenID Connect", Image: "/assets/img/auth/openid_connect.svg"}, | ||||
| 	"twitter":       {Name: "twitter", DisplayName: "Twitter", Image: "/assets/img/auth/twitter.png"}, | ||||
| 	"discord":       {Name: "discord", DisplayName: "Discord", Image: "/assets/img/auth/discord.png"}, | ||||
| 	"gitea": { | ||||
| 		Name: "gitea", DisplayName: "Gitea", Image: "/assets/img/auth/gitea.png", | ||||
| 		CustomURLMapping: &CustomURLMapping{ | ||||
| 			TokenURL:   gitea.TokenURL, | ||||
| 			AuthURL:    gitea.AuthURL, | ||||
| 			ProfileURL: gitea.ProfileURL, | ||||
| 		}, | ||||
| 	}, | ||||
| 	"nextcloud": { | ||||
| 		Name: "nextcloud", DisplayName: "Nextcloud", Image: "/assets/img/auth/nextcloud.png", | ||||
| 		CustomURLMapping: &CustomURLMapping{ | ||||
| 			TokenURL:   nextcloud.TokenURL, | ||||
| 			AuthURL:    nextcloud.AuthURL, | ||||
| 			ProfileURL: nextcloud.ProfileURL, | ||||
| 		}, | ||||
| 	}, | ||||
| 	"yandex": {Name: "yandex", DisplayName: "Yandex", Image: "/assets/img/auth/yandex.png"}, | ||||
| 	"mastodon": { | ||||
| 		Name: "mastodon", DisplayName: "Mastodon", Image: "/assets/img/auth/mastodon.png", | ||||
| 		CustomURLMapping: &CustomURLMapping{ | ||||
| 			AuthURL: mastodon.InstanceURL, | ||||
| 		}, | ||||
| 	}, | ||||
| var gothProviders = map[string]GothProvider{} | ||||
|  | ||||
| // RegisterGothProvider registers a GothProvider | ||||
| func RegisterGothProvider(provider GothProvider) { | ||||
| 	if _, has := gothProviders[provider.Name()]; has { | ||||
| 		log.Fatal("Duplicate oauth2provider type provided: %s", provider.Name()) | ||||
| 	} | ||||
| 	gothProviders[provider.Name()] = provider | ||||
| } | ||||
|  | ||||
| // GetOAuth2Providers returns the map of unconfigured OAuth2 providers | ||||
| // key is used as technical name (like in the callbackURL) | ||||
| // values to display | ||||
| func GetOAuth2Providers() []Provider { | ||||
| 	providers := make([]Provider, 0, len(gothProviders)) | ||||
|  | ||||
| 	for _, provider := range gothProviders { | ||||
| 		providers = append(providers, provider) | ||||
| 	} | ||||
| 	sort.Slice(providers, func(i, j int) bool { | ||||
| 		return providers[i].Name() < providers[j].Name() | ||||
| 	}) | ||||
| 	return providers | ||||
| } | ||||
|  | ||||
| // GetActiveOAuth2Providers returns the map of configured active OAuth2 providers | ||||
| @@ -103,9 +95,9 @@ func GetActiveOAuth2Providers() ([]string, map[string]Provider, error) { | ||||
| 	var orderedKeys []string | ||||
| 	providers := make(map[string]Provider) | ||||
| 	for _, source := range loginSources { | ||||
| 		prov := Providers[source.Cfg.(*Source).Provider] | ||||
| 		prov := gothProviders[source.Cfg.(*Source).Provider] | ||||
| 		if source.Cfg.(*Source).IconURL != "" { | ||||
| 			prov.Image = source.Cfg.(*Source).IconURL | ||||
| 			prov = &ImagedProvider{prov, source.Cfg.(*Source).IconURL} | ||||
| 		} | ||||
| 		providers[source.Name] = prov | ||||
| 		orderedKeys = append(orderedKeys, source.Name) | ||||
| @@ -116,9 +108,9 @@ func GetActiveOAuth2Providers() ([]string, map[string]Provider, error) { | ||||
| 	return orderedKeys, providers, nil | ||||
| } | ||||
|  | ||||
| // RegisterProvider register a OAuth2 provider in goth lib | ||||
| func RegisterProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL string, customURLMapping *CustomURLMapping) error { | ||||
| 	provider, err := createProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL, customURLMapping) | ||||
| // RegisterProviderWithGothic register a OAuth2 provider in goth lib | ||||
| func RegisterProviderWithGothic(providerName string, source *Source) error { | ||||
| 	provider, err := createProvider(providerName, source) | ||||
|  | ||||
| 	if err == nil && provider != nil { | ||||
| 		gothRWMutex.Lock() | ||||
| @@ -130,8 +122,8 @@ func RegisterProvider(providerName, providerType, clientID, clientSecret, openID | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // RemoveProvider removes the given OAuth2 provider from the goth lib | ||||
| func RemoveProvider(providerName string) { | ||||
| // RemoveProviderFromGothic removes the given OAuth2 provider from the goth lib | ||||
| func RemoveProviderFromGothic(providerName string) { | ||||
| 	gothRWMutex.Lock() | ||||
| 	defer gothRWMutex.Unlock() | ||||
|  | ||||
| @@ -147,114 +139,20 @@ func ClearProviders() { | ||||
| } | ||||
|  | ||||
| // used to create different types of goth providers | ||||
| func createProvider(providerName, providerType, clientID, clientSecret, openIDConnectAutoDiscoveryURL string, customURLMapping *CustomURLMapping) (goth.Provider, error) { | ||||
| func createProvider(providerName string, source *Source) (goth.Provider, error) { | ||||
| 	callbackURL := setting.AppURL + "user/oauth2/" + url.PathEscape(providerName) + "/callback" | ||||
|  | ||||
| 	var provider goth.Provider | ||||
| 	var err error | ||||
|  | ||||
| 	switch providerType { | ||||
| 	case "bitbucket": | ||||
| 		provider = bitbucket.New(clientID, clientSecret, callbackURL, "account") | ||||
| 	case "dropbox": | ||||
| 		provider = dropbox.New(clientID, clientSecret, callbackURL) | ||||
| 	case "facebook": | ||||
| 		provider = facebook.New(clientID, clientSecret, callbackURL, "email") | ||||
| 	case "github": | ||||
| 		authURL := github.AuthURL | ||||
| 		tokenURL := github.TokenURL | ||||
| 		profileURL := github.ProfileURL | ||||
| 		emailURL := github.EmailURL | ||||
| 		if customURLMapping != nil { | ||||
| 			if len(customURLMapping.AuthURL) > 0 { | ||||
| 				authURL = customURLMapping.AuthURL | ||||
| 			} | ||||
| 			if len(customURLMapping.TokenURL) > 0 { | ||||
| 				tokenURL = customURLMapping.TokenURL | ||||
| 			} | ||||
| 			if len(customURLMapping.ProfileURL) > 0 { | ||||
| 				profileURL = customURLMapping.ProfileURL | ||||
| 			} | ||||
| 			if len(customURLMapping.EmailURL) > 0 { | ||||
| 				emailURL = customURLMapping.EmailURL | ||||
| 			} | ||||
| 		} | ||||
| 		scopes := []string{} | ||||
| 		if setting.OAuth2Client.EnableAutoRegistration { | ||||
| 			scopes = append(scopes, "user:email") | ||||
| 		} | ||||
| 		provider = github.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, emailURL, scopes...) | ||||
| 	case "gitlab": | ||||
| 		authURL := gitlab.AuthURL | ||||
| 		tokenURL := gitlab.TokenURL | ||||
| 		profileURL := gitlab.ProfileURL | ||||
| 		if customURLMapping != nil { | ||||
| 			if len(customURLMapping.AuthURL) > 0 { | ||||
| 				authURL = customURLMapping.AuthURL | ||||
| 			} | ||||
| 			if len(customURLMapping.TokenURL) > 0 { | ||||
| 				tokenURL = customURLMapping.TokenURL | ||||
| 			} | ||||
| 			if len(customURLMapping.ProfileURL) > 0 { | ||||
| 				profileURL = customURLMapping.ProfileURL | ||||
| 			} | ||||
| 		} | ||||
| 		provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user") | ||||
| 	case "gplus": // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work | ||||
| 		scopes := []string{"email"} | ||||
| 		if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration { | ||||
| 			scopes = append(scopes, "profile") | ||||
| 		} | ||||
| 		provider = google.New(clientID, clientSecret, callbackURL, scopes...) | ||||
| 	case "openidConnect": | ||||
| 		if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...); err != nil { | ||||
| 			log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) | ||||
| 		} | ||||
| 	case "twitter": | ||||
| 		provider = twitter.NewAuthenticate(clientID, clientSecret, callbackURL) | ||||
| 	case "discord": | ||||
| 		provider = discord.New(clientID, clientSecret, callbackURL, discord.ScopeIdentify, discord.ScopeEmail) | ||||
| 	case "gitea": | ||||
| 		authURL := gitea.AuthURL | ||||
| 		tokenURL := gitea.TokenURL | ||||
| 		profileURL := gitea.ProfileURL | ||||
| 		if customURLMapping != nil { | ||||
| 			if len(customURLMapping.AuthURL) > 0 { | ||||
| 				authURL = customURLMapping.AuthURL | ||||
| 			} | ||||
| 			if len(customURLMapping.TokenURL) > 0 { | ||||
| 				tokenURL = customURLMapping.TokenURL | ||||
| 			} | ||||
| 			if len(customURLMapping.ProfileURL) > 0 { | ||||
| 				profileURL = customURLMapping.ProfileURL | ||||
| 			} | ||||
| 		} | ||||
| 		provider = gitea.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL) | ||||
| 	case "nextcloud": | ||||
| 		authURL := nextcloud.AuthURL | ||||
| 		tokenURL := nextcloud.TokenURL | ||||
| 		profileURL := nextcloud.ProfileURL | ||||
| 		if customURLMapping != nil { | ||||
| 			if len(customURLMapping.AuthURL) > 0 { | ||||
| 				authURL = customURLMapping.AuthURL | ||||
| 			} | ||||
| 			if len(customURLMapping.TokenURL) > 0 { | ||||
| 				tokenURL = customURLMapping.TokenURL | ||||
| 			} | ||||
| 			if len(customURLMapping.ProfileURL) > 0 { | ||||
| 				profileURL = customURLMapping.ProfileURL | ||||
| 			} | ||||
| 		} | ||||
| 		provider = nextcloud.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL) | ||||
| 	case "yandex": | ||||
| 		// See https://tech.yandex.com/passport/doc/dg/reference/response-docpage/ | ||||
| 		provider = yandex.New(clientID, clientSecret, callbackURL, "login:email", "login:info", "login:avatar") | ||||
| 	case "mastodon": | ||||
| 		instanceURL := mastodon.InstanceURL | ||||
| 		if customURLMapping != nil && len(customURLMapping.AuthURL) > 0 { | ||||
| 			instanceURL = customURLMapping.AuthURL | ||||
| 		} | ||||
| 		provider = mastodon.NewCustomisedURL(clientID, clientSecret, callbackURL, instanceURL) | ||||
| 	p, ok := gothProviders[source.Provider] | ||||
| 	if !ok { | ||||
| 		return nil, models.ErrLoginSourceNotActived | ||||
| 	} | ||||
|  | ||||
| 	provider, err = p.CreateGothProvider(providerName, callbackURL, source) | ||||
| 	if err != nil { | ||||
| 		return provider, err | ||||
| 	} | ||||
|  | ||||
| 	// always set the name if provider is created so we can support multiple setups of 1 provider | ||||
|   | ||||
							
								
								
									
										33
									
								
								services/auth/source/oauth2/providers_base.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								services/auth/source/oauth2/providers_base.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| // Copyright 2021 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package oauth2 | ||||
|  | ||||
| // BaseProvider represents a common base for Provider | ||||
| type BaseProvider struct { | ||||
| 	name        string | ||||
| 	displayName string | ||||
| } | ||||
|  | ||||
| // Name provides the technical name for this provider | ||||
| func (b *BaseProvider) Name() string { | ||||
| 	return b.name | ||||
| } | ||||
|  | ||||
| // DisplayName returns the friendly name for this provider | ||||
| func (b *BaseProvider) DisplayName() string { | ||||
| 	return b.displayName | ||||
| } | ||||
|  | ||||
| // Image returns an image path for this provider | ||||
| func (b *BaseProvider) Image() string { | ||||
| 	return "/assets/img/auth/" + b.name + ".png" | ||||
| } | ||||
|  | ||||
| // CustomURLSettings returns the custom url settings for this provider | ||||
| func (b *BaseProvider) CustomURLSettings() *CustomURLSettings { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| var _ (Provider) = &BaseProvider{} | ||||
							
								
								
									
										118
									
								
								services/auth/source/oauth2/providers_custom.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								services/auth/source/oauth2/providers_custom.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| // Copyright 2021 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package oauth2 | ||||
|  | ||||
| import ( | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| 	"github.com/markbates/goth/providers/azureadv2" | ||||
| 	"github.com/markbates/goth/providers/gitea" | ||||
| 	"github.com/markbates/goth/providers/github" | ||||
| 	"github.com/markbates/goth/providers/gitlab" | ||||
| 	"github.com/markbates/goth/providers/mastodon" | ||||
| 	"github.com/markbates/goth/providers/nextcloud" | ||||
| ) | ||||
|  | ||||
| // CustomProviderNewFn creates a goth.Provider using a custom url mapping | ||||
| type CustomProviderNewFn func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) | ||||
|  | ||||
| // CustomProvider is a GothProvider that has CustomURL features | ||||
| type CustomProvider struct { | ||||
| 	BaseProvider | ||||
| 	customURLSettings *CustomURLSettings | ||||
| 	newFn             CustomProviderNewFn | ||||
| } | ||||
|  | ||||
| // CustomURLSettings returns the CustomURLSettings for this provider | ||||
| func (c *CustomProvider) CustomURLSettings() *CustomURLSettings { | ||||
| 	return c.customURLSettings | ||||
| } | ||||
|  | ||||
| // CreateGothProvider creates a GothProvider from this Provider | ||||
| func (c *CustomProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) { | ||||
| 	custom := c.customURLSettings.OverrideWith(source.CustomURLMapping) | ||||
|  | ||||
| 	return c.newFn(source.ClientID, source.ClientSecret, callbackURL, custom) | ||||
| } | ||||
|  | ||||
| // NewCustomProvider is a constructor function for custom providers | ||||
| func NewCustomProvider(name, displayName string, customURLSetting *CustomURLSettings, newFn CustomProviderNewFn) *CustomProvider { | ||||
| 	return &CustomProvider{ | ||||
| 		BaseProvider: BaseProvider{ | ||||
| 			name:        name, | ||||
| 			displayName: displayName, | ||||
| 		}, | ||||
| 		customURLSettings: customURLSetting, | ||||
| 		newFn:             newFn, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var _ (GothProvider) = &CustomProvider{} | ||||
|  | ||||
| func init() { | ||||
| 	RegisterGothProvider(NewCustomProvider( | ||||
| 		"github", "GitHub", &CustomURLSettings{ | ||||
| 			TokenURL:   availableAttribute(gitea.TokenURL), | ||||
| 			AuthURL:    availableAttribute(github.AuthURL), | ||||
| 			ProfileURL: availableAttribute(github.ProfileURL), | ||||
| 			EmailURL:   availableAttribute(github.EmailURL), | ||||
| 		}, | ||||
| 		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { | ||||
| 			scopes := []string{} | ||||
| 			if setting.OAuth2Client.EnableAutoRegistration { | ||||
| 				scopes = append(scopes, "user:email") | ||||
| 			} | ||||
| 			return github.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, custom.EmailURL, scopes...), nil | ||||
| 		})) | ||||
|  | ||||
| 	RegisterGothProvider(NewCustomProvider( | ||||
| 		"gitlab", "GitLab", &CustomURLSettings{ | ||||
| 			AuthURL:    availableAttribute(gitlab.AuthURL), | ||||
| 			TokenURL:   availableAttribute(gitlab.TokenURL), | ||||
| 			ProfileURL: availableAttribute(gitlab.ProfileURL), | ||||
| 		}, func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { | ||||
| 			return gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, "read_user"), nil | ||||
| 		})) | ||||
|  | ||||
| 	RegisterGothProvider(NewCustomProvider( | ||||
| 		"gitea", "Gitea", &CustomURLSettings{ | ||||
| 			TokenURL:   requiredAttribute(gitea.TokenURL), | ||||
| 			AuthURL:    requiredAttribute(gitea.AuthURL), | ||||
| 			ProfileURL: requiredAttribute(gitea.ProfileURL), | ||||
| 		}, | ||||
| 		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { | ||||
| 			return gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL), nil | ||||
| 		})) | ||||
|  | ||||
| 	RegisterGothProvider(NewCustomProvider( | ||||
| 		"nextcloud", "Nextcloud", &CustomURLSettings{ | ||||
| 			TokenURL:   requiredAttribute(nextcloud.TokenURL), | ||||
| 			AuthURL:    requiredAttribute(nextcloud.AuthURL), | ||||
| 			ProfileURL: requiredAttribute(nextcloud.ProfileURL), | ||||
| 		}, | ||||
| 		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { | ||||
| 			return nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL), nil | ||||
| 		})) | ||||
|  | ||||
| 	RegisterGothProvider(NewCustomProvider( | ||||
| 		"mastodon", "Mastodon", &CustomURLSettings{ | ||||
| 			AuthURL: requiredAttribute(mastodon.InstanceURL), | ||||
| 		}, | ||||
| 		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { | ||||
| 			return mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL), nil | ||||
| 		})) | ||||
|  | ||||
| 	RegisterGothProvider(NewCustomProvider( | ||||
| 		"azureadv2", "Azure AD v2", &CustomURLSettings{ | ||||
| 			Tenant: requiredAttribute("organizations"), | ||||
| 		}, | ||||
| 		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) { | ||||
| 			return azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{ | ||||
| 				Tenant: azureadv2.TenantType(custom.Tenant), | ||||
| 			}), nil | ||||
| 		}, | ||||
| 	)) | ||||
| } | ||||
							
								
								
									
										52
									
								
								services/auth/source/oauth2/providers_openid.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								services/auth/source/oauth2/providers_openid.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| // Copyright 2021 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package oauth2 | ||||
|  | ||||
| import ( | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| 	"github.com/markbates/goth/providers/openidConnect" | ||||
| ) | ||||
|  | ||||
| // OpenIDProvider is a GothProvider for OpenID | ||||
| type OpenIDProvider struct { | ||||
| } | ||||
|  | ||||
| // Name provides the technical name for this provider | ||||
| func (o *OpenIDProvider) Name() string { | ||||
| 	return "openidconnect" | ||||
| } | ||||
|  | ||||
| // DisplayName returns the friendly name for this provider | ||||
| func (o *OpenIDProvider) DisplayName() string { | ||||
| 	return "OpenID Connect" | ||||
| } | ||||
|  | ||||
| // Image returns an image path for this provider | ||||
| func (o *OpenIDProvider) Image() string { | ||||
| 	return "/assets/img/auth/openid_connect.svg" | ||||
| } | ||||
|  | ||||
| // CreateGothProvider creates a GothProvider from this Provider | ||||
| func (o *OpenIDProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) { | ||||
| 	provider, err := openidConnect.New(source.ClientID, source.ClientSecret, callbackURL, source.OpenIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...) | ||||
| 	if err != nil { | ||||
| 		log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err) | ||||
| 	} | ||||
| 	return provider, err | ||||
| } | ||||
|  | ||||
| // CustomURLSettings returns the custom url settings for this provider | ||||
| func (o *OpenIDProvider) CustomURLSettings() *CustomURLSettings { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| var _ (GothProvider) = &OpenIDProvider{} | ||||
|  | ||||
| func init() { | ||||
| 	RegisterGothProvider(&OpenIDProvider{}) | ||||
| } | ||||
							
								
								
									
										108
									
								
								services/auth/source/oauth2/providers_simple.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								services/auth/source/oauth2/providers_simple.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| // Copyright 2021 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package oauth2 | ||||
|  | ||||
| import ( | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| 	"github.com/markbates/goth/providers/azuread" | ||||
| 	"github.com/markbates/goth/providers/bitbucket" | ||||
| 	"github.com/markbates/goth/providers/discord" | ||||
| 	"github.com/markbates/goth/providers/dropbox" | ||||
| 	"github.com/markbates/goth/providers/facebook" | ||||
| 	"github.com/markbates/goth/providers/google" | ||||
| 	"github.com/markbates/goth/providers/microsoftonline" | ||||
| 	"github.com/markbates/goth/providers/twitter" | ||||
| 	"github.com/markbates/goth/providers/yandex" | ||||
| ) | ||||
|  | ||||
| // SimpleProviderNewFn create goth.Providers without custom url features | ||||
| type SimpleProviderNewFn func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider | ||||
|  | ||||
| // SimpleProvider is a GothProvider which does not have custom url features | ||||
| type SimpleProvider struct { | ||||
| 	BaseProvider | ||||
| 	scopes []string | ||||
| 	newFn  SimpleProviderNewFn | ||||
| } | ||||
|  | ||||
| // CreateGothProvider creates a GothProvider from this Provider | ||||
| func (c *SimpleProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) { | ||||
| 	return c.newFn(source.ClientID, source.ClientSecret, callbackURL, c.scopes...), nil | ||||
| } | ||||
|  | ||||
| // NewSimpleProvider is a constructor function for simple providers | ||||
| func NewSimpleProvider(name, displayName string, scopes []string, newFn SimpleProviderNewFn) *SimpleProvider { | ||||
| 	return &SimpleProvider{ | ||||
| 		BaseProvider: BaseProvider{ | ||||
| 			name:        name, | ||||
| 			displayName: displayName, | ||||
| 		}, | ||||
| 		scopes: scopes, | ||||
| 		newFn:  newFn, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var _ (GothProvider) = &SimpleProvider{} | ||||
|  | ||||
| func init() { | ||||
| 	RegisterGothProvider( | ||||
| 		NewSimpleProvider("bitbucket", "Bitbucket", []string{"account"}, | ||||
| 			func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 				return bitbucket.New(clientKey, secret, callbackURL, scopes...) | ||||
| 			})) | ||||
|  | ||||
| 	RegisterGothProvider( | ||||
| 		NewSimpleProvider("dropbox", "Dropbox", nil, | ||||
| 			func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 				return dropbox.New(clientKey, secret, callbackURL, scopes...) | ||||
| 			})) | ||||
|  | ||||
| 	RegisterGothProvider(NewSimpleProvider("facebook", "Facebook", nil, | ||||
| 		func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 			return facebook.New(clientKey, secret, callbackURL, scopes...) | ||||
| 		})) | ||||
|  | ||||
| 	// named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work | ||||
| 	RegisterGothProvider(NewSimpleProvider("gplus", "Google", []string{"email"}, | ||||
| 		func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 			if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration { | ||||
| 				scopes = append(scopes, "profile") | ||||
| 			} | ||||
| 			return google.New(clientKey, secret, callbackURL, scopes...) | ||||
| 		})) | ||||
|  | ||||
| 	RegisterGothProvider(NewSimpleProvider("twitter", "Twitter", nil, | ||||
| 		func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 			return twitter.New(clientKey, secret, callbackURL) | ||||
| 		})) | ||||
|  | ||||
| 	RegisterGothProvider(NewSimpleProvider("discord", "Discord", []string{discord.ScopeIdentify, discord.ScopeEmail}, | ||||
| 		func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 			return discord.New(clientKey, secret, callbackURL, scopes...) | ||||
| 		})) | ||||
|  | ||||
| 	// See https://tech.yandex.com/passport/doc/dg/reference/response-docpage/ | ||||
| 	RegisterGothProvider(NewSimpleProvider("yandex", "Yandex", []string{"login:email", "login:info", "login:avatar"}, | ||||
| 		func(clientKey, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 			return yandex.New(clientKey, secret, callbackURL, scopes...) | ||||
| 		})) | ||||
|  | ||||
| 	RegisterGothProvider(NewSimpleProvider( | ||||
| 		"azuread", "Azure AD", nil, | ||||
| 		func(clientID, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 			return azuread.New(clientID, secret, callbackURL, nil, scopes...) | ||||
| 		}, | ||||
| 	)) | ||||
|  | ||||
| 	RegisterGothProvider(NewSimpleProvider( | ||||
| 		"microsoftonline", "Microsoft Online", nil, | ||||
| 		func(clientID, secret, callbackURL string, scopes ...string) goth.Provider { | ||||
| 			return microsoftonline.New(clientID, secret, callbackURL, scopes...) | ||||
| 		}, | ||||
| 	)) | ||||
|  | ||||
| } | ||||
							
								
								
									
										19
									
								
								services/auth/source/oauth2/source_name.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								services/auth/source/oauth2/source_name.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| // Copyright 2021 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package oauth2 | ||||
|  | ||||
| // Name returns the provider name of this source | ||||
| func (source *Source) Name() string { | ||||
| 	return source.Provider | ||||
| } | ||||
|  | ||||
| // DisplayName returns the display name of this source | ||||
| func (source *Source) DisplayName() string { | ||||
| 	provider, has := gothProviders[source.Provider] | ||||
| 	if !has { | ||||
| 		return source.Provider | ||||
| 	} | ||||
| 	return provider.DisplayName() | ||||
| } | ||||
| @@ -10,13 +10,13 @@ import ( | ||||
|  | ||||
| // RegisterSource causes an OAuth2 configuration to be registered | ||||
| func (source *Source) RegisterSource() error { | ||||
| 	err := RegisterProvider(source.loginSource.Name, source.Provider, source.ClientID, source.ClientSecret, source.OpenIDConnectAutoDiscoveryURL, source.CustomURLMapping) | ||||
| 	err := RegisterProviderWithGothic(source.loginSource.Name, source) | ||||
| 	return wrapOpenIDConnectInitializeError(err, source.loginSource.Name, source) | ||||
| } | ||||
|  | ||||
| // UnregisterSource causes an OAuth2 configuration to be unregistered | ||||
| func (source *Source) UnregisterSource() error { | ||||
| 	RemoveProvider(source.loginSource.Name) | ||||
| 	RemoveProviderFromGothic(source.loginSource.Name) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -6,19 +6,73 @@ package oauth2 | ||||
|  | ||||
| // CustomURLMapping describes the urls values to use when customizing OAuth2 provider URLs | ||||
| type CustomURLMapping struct { | ||||
| 	AuthURL    string | ||||
| 	TokenURL   string | ||||
| 	ProfileURL string | ||||
| 	EmailURL   string | ||||
| 	AuthURL    string `json:",omitempty"` | ||||
| 	TokenURL   string `json:",omitempty"` | ||||
| 	ProfileURL string `json:",omitempty"` | ||||
| 	EmailURL   string `json:",omitempty"` | ||||
| 	Tenant     string `json:",omitempty"` | ||||
| } | ||||
|  | ||||
| // DefaultCustomURLMappings contains the map of default URL's for OAuth2 providers that are allowed to have custom urls | ||||
| // key is used to map the OAuth2Provider | ||||
| // value is the mapping as defined for the OAuth2Provider | ||||
| var DefaultCustomURLMappings = map[string]*CustomURLMapping{ | ||||
| 	"github":    Providers["github"].CustomURLMapping, | ||||
| 	"gitlab":    Providers["gitlab"].CustomURLMapping, | ||||
| 	"gitea":     Providers["gitea"].CustomURLMapping, | ||||
| 	"nextcloud": Providers["nextcloud"].CustomURLMapping, | ||||
| 	"mastodon":  Providers["mastodon"].CustomURLMapping, | ||||
| // CustomURLSettings describes the urls values and availability to use when customizing OAuth2 provider URLs | ||||
| type CustomURLSettings struct { | ||||
| 	AuthURL    Attribute `json:",omitempty"` | ||||
| 	TokenURL   Attribute `json:",omitempty"` | ||||
| 	ProfileURL Attribute `json:",omitempty"` | ||||
| 	EmailURL   Attribute `json:",omitempty"` | ||||
| 	Tenant     Attribute `json:",omitempty"` | ||||
| } | ||||
|  | ||||
| // Attribute describes the availability, and required status for a custom url configuration | ||||
| type Attribute struct { | ||||
| 	Value     string | ||||
| 	Available bool | ||||
| 	Required  bool | ||||
| } | ||||
|  | ||||
| func availableAttribute(value string) Attribute { | ||||
| 	return Attribute{Value: value, Available: true} | ||||
| } | ||||
|  | ||||
| func requiredAttribute(value string) Attribute { | ||||
| 	return Attribute{Value: value, Available: true, Required: true} | ||||
| } | ||||
|  | ||||
| // Required is true if any attribute is required | ||||
| func (c *CustomURLSettings) Required() bool { | ||||
| 	if c == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	if c.AuthURL.Required || c.EmailURL.Required || c.ProfileURL.Required || c.TokenURL.Required || c.Tenant.Required { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // OverrideWith copies the current customURLMapping and overrides it with values from the provided mapping | ||||
| func (c *CustomURLSettings) OverrideWith(override *CustomURLMapping) *CustomURLMapping { | ||||
| 	custom := &CustomURLMapping{ | ||||
| 		AuthURL:    c.AuthURL.Value, | ||||
| 		TokenURL:   c.TokenURL.Value, | ||||
| 		ProfileURL: c.ProfileURL.Value, | ||||
| 		EmailURL:   c.EmailURL.Value, | ||||
| 		Tenant:     c.Tenant.Value, | ||||
| 	} | ||||
| 	if override != nil { | ||||
| 		if len(override.AuthURL) > 0 && c.AuthURL.Available { | ||||
| 			custom.AuthURL = override.AuthURL | ||||
| 		} | ||||
| 		if len(override.TokenURL) > 0 && c.TokenURL.Available { | ||||
| 			custom.TokenURL = override.TokenURL | ||||
| 		} | ||||
| 		if len(override.ProfileURL) > 0 && c.ProfileURL.Available { | ||||
| 			custom.ProfileURL = override.ProfileURL | ||||
| 		} | ||||
| 		if len(override.EmailURL) > 0 && c.EmailURL.Available { | ||||
| 			custom.EmailURL = override.EmailURL | ||||
| 		} | ||||
| 		if len(override.Tenant) > 0 && c.Tenant.Available { | ||||
| 			custom.Tenant = override.Tenant | ||||
| 		} | ||||
| 	} | ||||
| 	return custom | ||||
| } | ||||
|   | ||||
| @@ -62,6 +62,7 @@ type AuthenticationForm struct { | ||||
| 	Oauth2ProfileURL              string | ||||
| 	Oauth2EmailURL                string | ||||
| 	Oauth2IconURL                 string | ||||
| 	Oauth2Tenant                  string | ||||
| 	SSPIAutoCreateUsers           bool | ||||
| 	SSPIAutoActivateUsers         bool | ||||
| 	SSPIStripDomainNames          bool | ||||
|   | ||||
| @@ -203,8 +203,8 @@ | ||||
| 							<div class="text">{{.CurrentOAuth2Provider.DisplayName}}</div> | ||||
| 							{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||
| 							<div class="menu"> | ||||
| 								{{range $key, $value := .OAuth2Providers}} | ||||
| 									<div class="item" data-value="{{$key}}">{{$value.DisplayName}}</div> | ||||
| 								{{range .OAuth2Providers}} | ||||
| 									<div class="item" data-value="{{.Name}}">{{.DisplayName}}</div> | ||||
| 								{{end}} | ||||
| 							</div> | ||||
| 						</div> | ||||
| @@ -248,11 +248,18 @@ | ||||
| 						<label for="oauth2_email_url">{{.i18n.Tr "admin.auths.oauth2_emailURL"}}</label> | ||||
| 						<input id="oauth2_email_url" name="oauth2_email_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.EmailURL}}{{end}}"> | ||||
| 					</div> | ||||
| 					{{if .OAuth2DefaultCustomURLMappings}}{{range $key, $value := .OAuth2DefaultCustomURLMappings}} | ||||
| 					<input id="{{$key}}_token_url" value="{{$value.TokenURL}}" type="hidden" /> | ||||
| 					<input id="{{$key}}_auth_url" value="{{$value.AuthURL}}" type="hidden" /> | ||||
| 					<input id="{{$key}}_profile_url" value="{{$value.ProfileURL}}" type="hidden" /> | ||||
| 					<input id="{{$key}}_email_url" value="{{$value.EmailURL}}" type="hidden" /> | ||||
| 					<div class="oauth2_use_custom_url_field oauth2_tenant required field"> | ||||
| 						<label for="oauth2_tenant">{{.i18n.Tr "admin.auths.oauth2_tenant"}}</label> | ||||
| 						<input id="oauth2_tenant" name="oauth2_tenant" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.Tenant}}{{end}}"> | ||||
| 					</div> | ||||
|  | ||||
| 					{{range .OAuth2Providers}}{{if .CustomURLSettings}} | ||||
| 						<input id="{{.Name}}_customURLSettings" type="hidden" data-required="{{.CustomURLSettings.Required}}" data-available="true"> | ||||
| 						<input id="{{.Name}}_token_url" value="{{.CustomURLSettings.TokenURL.Value}}" data-available="{{.CustomURLSettings.TokenURL.Available}}" data-required="{{.CustomURLSettings.TokenURL.Required}}" type="hidden" /> | ||||
| 						<input id="{{.Name}}_auth_url" value="{{.CustomURLSettings.AuthURL.Value}}" data-available="{{.CustomURLSettings.AuthURL.Available}}" data-required="{{.CustomURLSettings.AuthURL.Required}}" type="hidden" /> | ||||
| 						<input id="{{.Name}}_profile_url" value="{{.CustomURLSettings.ProfileURL.Value}}" data-available="{{.CustomURLSettings.ProfileURL.Available}}" data-required="{{.CustomURLSettings.ProfileURL.Required}}" type="hidden" /> | ||||
| 						<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden" /> | ||||
| 						<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden" /> | ||||
| 					{{end}}{{end}} | ||||
| 				{{end}} | ||||
|  | ||||
|   | ||||
| @@ -2,12 +2,12 @@ | ||||
| 	<div class="inline required field"> | ||||
| 		<label>{{.i18n.Tr "admin.auths.oauth2_provider"}}</label> | ||||
| 		<div class="ui selection type dropdown"> | ||||
| 			<input type="hidden" id="oauth2_provider" name="oauth2_provider" value="{{.oauth2_provider}}"> | ||||
| 			<div class="text">{{.oauth2_provider}}</div> | ||||
| 			<input type="hidden" id="oauth2_provider" name="oauth2_provider" value="{{.oauth2_provider.Name}}"> | ||||
| 			<div class="text">{{.oauth2_provider.Name}}</div> | ||||
| 			{{svg "octicon-triangle-down" 14 "dropdown icon"}} | ||||
| 			<div class="menu"> | ||||
| 				{{range $key, $value := .OAuth2Providers}} | ||||
| 					<div class="item" data-value="{{$key}}">{{$value.DisplayName}}</div> | ||||
| 				{{range .OAuth2Providers}} | ||||
| 					<div class="item" data-value="{{.Name}}">{{.DisplayName}}</div> | ||||
| 				{{end}} | ||||
| 			</div> | ||||
| 		</div> | ||||
| @@ -51,12 +51,17 @@ | ||||
| 		<label for="oauth2_email_url">{{.i18n.Tr "admin.auths.oauth2_emailURL"}}</label> | ||||
| 		<input id="oauth2_email_url" name="oauth2_email_url" value="{{.oauth2_email_url}}"> | ||||
| 	</div> | ||||
| 	{{if .OAuth2DefaultCustomURLMappings}} | ||||
| 		{{range $key, $value := .OAuth2DefaultCustomURLMappings}} | ||||
| 			<input id="{{$key}}_token_url" value="{{$value.TokenURL}}" type="hidden" /> | ||||
| 			<input id="{{$key}}_auth_url" value="{{$value.AuthURL}}" type="hidden" /> | ||||
| 			<input id="{{$key}}_profile_url" value="{{$value.ProfileURL}}" type="hidden" /> | ||||
| 			<input id="{{$key}}_email_url" value="{{$value.EmailURL}}" type="hidden" /> | ||||
| 		{{end}} | ||||
| 	{{end}} | ||||
| 	<div class="oauth2_use_custom_url_field oauth2_tenant required field"> | ||||
| 		<label for="oauth2_tenant">{{.i18n.Tr "admin.auths.oauth2_tenant"}}</label> | ||||
| 		<input id="oauth2_tenant" name="oauth2_tenant" value="{{.oauth2_tenant}}"> | ||||
| 	</div> | ||||
|  | ||||
| 	{{range .OAuth2Providers}}{{if .CustomURLSettings}} | ||||
| 		<input id="{{.Name}}_customURLSettings" type="hidden" data-required="{{.CustomURLSettings.Required}}" data-available="true"> | ||||
| 		<input id="{{.Name}}_token_url" value="{{.CustomURLSettings.TokenURL.Value}}" data-available="{{.CustomURLSettings.TokenURL.Available}}" data-required="{{.CustomURLSettings.TokenURL.Required}}" type="hidden" /> | ||||
| 		<input id="{{.Name}}_auth_url" value="{{.CustomURLSettings.AuthURL.Value}}" data-available="{{.CustomURLSettings.AuthURL.Available}}" data-required="{{.CustomURLSettings.AuthURL.Required}}" type="hidden" /> | ||||
| 		<input id="{{.Name}}_profile_url" value="{{.CustomURLSettings.ProfileURL.Value}}" data-available="{{.CustomURLSettings.ProfileURL.Available}}" data-required="{{.CustomURLSettings.ProfileURL.Required}}" type="hidden" /> | ||||
| 		<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden" /> | ||||
| 		<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden" /> | ||||
| 	{{end}}{{end}} | ||||
| </div> | ||||
|   | ||||
							
								
								
									
										22
									
								
								vendor/github.com/markbates/going/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/markbates/going/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| Copyright (c) 2014 Mark Bates | ||||
|  | ||||
| MIT License | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										36
									
								
								vendor/github.com/markbates/going/defaults/defaults.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								vendor/github.com/markbates/going/defaults/defaults.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| package defaults | ||||
|  | ||||
| func String(s1, s2 string) string { | ||||
| 	if s1 == "" { | ||||
| 		return s2 | ||||
| 	} | ||||
| 	return s1 | ||||
| } | ||||
|  | ||||
| func Int(i1, i2 int) int { | ||||
| 	if i1 == 0 { | ||||
| 		return i2 | ||||
| 	} | ||||
| 	return i1 | ||||
| } | ||||
|  | ||||
| func Int64(i1, i2 int64) int64 { | ||||
| 	if i1 == 0 { | ||||
| 		return i2 | ||||
| 	} | ||||
| 	return i1 | ||||
| } | ||||
|  | ||||
| func Float32(i1, i2 float32) float32 { | ||||
| 	if i1 == 0.0 { | ||||
| 		return i2 | ||||
| 	} | ||||
| 	return i1 | ||||
| } | ||||
|  | ||||
| func Float64(i1, i2 float64) float64 { | ||||
| 	if i1 == 0.0 { | ||||
| 		return i2 | ||||
| 	} | ||||
| 	return i1 | ||||
| } | ||||
							
								
								
									
										187
									
								
								vendor/github.com/markbates/goth/providers/azuread/azuread.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								vendor/github.com/markbates/goth/providers/azuread/azuread.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| // Package azuread implements the OAuth2 protocol for authenticating users through AzureAD. | ||||
| // This package can be used as a reference implementation of an OAuth2 provider for Goth. | ||||
| // To use microsoft personal account use microsoftonline provider | ||||
| package azuread | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	authURL          string = "https://login.microsoftonline.com/common/oauth2/authorize" | ||||
| 	tokenURL         string = "https://login.microsoftonline.com/common/oauth2/token" | ||||
| 	endpointProfile  string = "https://graph.windows.net/me?api-version=1.6" | ||||
| 	graphAPIResource string = "https://graph.windows.net/" | ||||
| ) | ||||
|  | ||||
| // New creates a new AzureAD provider, and sets up important connection details. | ||||
| // You should always call `AzureAD.New` to get a new Provider. Never try to create | ||||
| // one manually. | ||||
| func New(clientKey, secret, callbackURL string, resources []string, scopes ...string) *Provider { | ||||
| 	p := &Provider{ | ||||
| 		ClientKey:    clientKey, | ||||
| 		Secret:       secret, | ||||
| 		CallbackURL:  callbackURL, | ||||
| 		providerName: "azuread", | ||||
| 	} | ||||
|  | ||||
| 	p.resources = make([]string, 0, 1+len(resources)) | ||||
| 	p.resources = append(p.resources, graphAPIResource) | ||||
| 	p.resources = append(p.resources, resources...) | ||||
|  | ||||
| 	p.config = newConfig(p, scopes) | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| // Provider is the implementation of `goth.Provider` for accessing AzureAD. | ||||
| type Provider struct { | ||||
| 	ClientKey    string | ||||
| 	Secret       string | ||||
| 	CallbackURL  string | ||||
| 	HTTPClient   *http.Client | ||||
| 	config       *oauth2.Config | ||||
| 	providerName string | ||||
| 	resources    []string | ||||
| } | ||||
|  | ||||
| // Name is the name used to retrieve this provider later. | ||||
| func (p *Provider) Name() string { | ||||
| 	return p.providerName | ||||
| } | ||||
|  | ||||
| // SetName is to update the name of the provider (needed in case of multiple providers of 1 type) | ||||
| func (p *Provider) SetName(name string) { | ||||
| 	p.providerName = name | ||||
| } | ||||
|  | ||||
| // Client is HTTP client to be used in all fetch operations. | ||||
| func (p *Provider) Client() *http.Client { | ||||
| 	return goth.HTTPClientWithFallBack(p.HTTPClient) | ||||
| } | ||||
|  | ||||
| // Debug is a no-op for the package. | ||||
| func (p *Provider) Debug(debug bool) {} | ||||
|  | ||||
| // BeginAuth asks AzureAD for an authentication end-point. | ||||
| func (p *Provider) BeginAuth(state string) (goth.Session, error) { | ||||
| 	authURL := p.config.AuthCodeURL(state) | ||||
|  | ||||
| 	// Azure ad requires at least one resource | ||||
| 	authURL += "&resource=" + url.QueryEscape(strings.Join(p.resources, " ")) | ||||
|  | ||||
| 	return &Session{ | ||||
| 		AuthURL: authURL, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // FetchUser will go to AzureAD and access basic information about the user. | ||||
| func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { | ||||
| 	msSession := session.(*Session) | ||||
| 	user := goth.User{ | ||||
| 		AccessToken: msSession.AccessToken, | ||||
| 		Provider:    p.Name(), | ||||
| 		ExpiresAt:   msSession.ExpiresAt, | ||||
| 	} | ||||
|  | ||||
| 	if user.AccessToken == "" { | ||||
| 		return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName) | ||||
| 	} | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", endpointProfile, nil) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
| 	req.Header.Set(authorizationHeader(msSession)) | ||||
|  | ||||
| 	response, err := p.Client().Do(req) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
| 	defer response.Body.Close() | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode) | ||||
| 	} | ||||
|  | ||||
| 	err = userFromReader(response.Body, &user) | ||||
| 	return user, err | ||||
| } | ||||
|  | ||||
| //RefreshTokenAvailable refresh token is provided by auth provider or not | ||||
| func (p *Provider) RefreshTokenAvailable() bool { | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| //RefreshToken get new access token based on the refresh token | ||||
| func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { | ||||
| 	token := &oauth2.Token{RefreshToken: refreshToken} | ||||
| 	ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token) | ||||
| 	newToken, err := ts.Token() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return newToken, err | ||||
| } | ||||
|  | ||||
| func newConfig(provider *Provider, scopes []string) *oauth2.Config { | ||||
| 	c := &oauth2.Config{ | ||||
| 		ClientID:     provider.ClientKey, | ||||
| 		ClientSecret: provider.Secret, | ||||
| 		RedirectURL:  provider.CallbackURL, | ||||
| 		Endpoint: oauth2.Endpoint{ | ||||
| 			AuthURL:  authURL, | ||||
| 			TokenURL: tokenURL, | ||||
| 		}, | ||||
| 		Scopes: []string{}, | ||||
| 	} | ||||
|  | ||||
| 	if len(scopes) > 0 { | ||||
| 		for _, scope := range scopes { | ||||
| 			c.Scopes = append(c.Scopes, scope) | ||||
| 		} | ||||
| 	} else { | ||||
| 		c.Scopes = append(c.Scopes, "user_impersonation") | ||||
| 	} | ||||
|  | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| func userFromReader(r io.Reader, user *goth.User) error { | ||||
| 	u := struct { | ||||
| 		Name              string `json:"name"` | ||||
| 		Email             string `json:"mail"` | ||||
| 		FirstName         string `json:"givenName"` | ||||
| 		LastName          string `json:"surname"` | ||||
| 		NickName          string `json:"mailNickname"` | ||||
| 		UserPrincipalName string `json:"userPrincipalName"` | ||||
| 		Location          string `json:"usageLocation"` | ||||
| 	}{} | ||||
|  | ||||
| 	err := json.NewDecoder(r).Decode(&u) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	user.Email = u.Email | ||||
| 	user.Name = u.Name | ||||
| 	user.FirstName = u.FirstName | ||||
| 	user.LastName = u.LastName | ||||
| 	user.NickName = u.Name | ||||
| 	user.Location = u.Location | ||||
| 	user.UserID = u.UserPrincipalName //AzureAD doesn't provide separate user_id | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func authorizationHeader(session *Session) (string, string) { | ||||
| 	return "Authorization", fmt.Sprintf("Bearer %s", session.AccessToken) | ||||
| } | ||||
							
								
								
									
										63
									
								
								vendor/github.com/markbates/goth/providers/azuread/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								vendor/github.com/markbates/goth/providers/azuread/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| package azuread | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| ) | ||||
|  | ||||
| // Session is the implementation of `goth.Session` for accessing AzureAD. | ||||
| type Session struct { | ||||
| 	AuthURL      string | ||||
| 	AccessToken  string | ||||
| 	RefreshToken string | ||||
| 	ExpiresAt    time.Time | ||||
| } | ||||
|  | ||||
| // GetAuthURL will return the URL set by calling the `BeginAuth` function on the Facebook provider. | ||||
| func (s Session) GetAuthURL() (string, error) { | ||||
| 	if s.AuthURL == "" { | ||||
| 		return "", errors.New(goth.NoAuthUrlErrorMessage) | ||||
| 	} | ||||
|  | ||||
| 	return s.AuthURL, nil | ||||
| } | ||||
|  | ||||
| // Authorize the session with AzureAD and return the access token to be stored for future use. | ||||
| func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) { | ||||
| 	p := provider.(*Provider) | ||||
| 	token, err := p.config.Exchange(goth.ContextForClient(p.Client()), params.Get("code")) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if !token.Valid() { | ||||
| 		return "", errors.New("invalid token received from provider") | ||||
| 	} | ||||
|  | ||||
| 	s.AccessToken = token.AccessToken | ||||
| 	s.RefreshToken = token.RefreshToken | ||||
| 	s.ExpiresAt = token.Expiry | ||||
|  | ||||
| 	return token.AccessToken, err | ||||
| } | ||||
|  | ||||
| // Marshal the session into a string | ||||
| func (s Session) Marshal() string { | ||||
| 	b, _ := json.Marshal(s) | ||||
| 	return string(b) | ||||
| } | ||||
|  | ||||
| func (s Session) String() string { | ||||
| 	return s.Marshal() | ||||
| } | ||||
|  | ||||
| // UnmarshalSession wil unmarshal a JSON string into a session. | ||||
| func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { | ||||
| 	session := &Session{} | ||||
| 	err := json.NewDecoder(strings.NewReader(data)).Decode(session) | ||||
| 	return session, err | ||||
| } | ||||
							
								
								
									
										233
									
								
								vendor/github.com/markbates/goth/providers/azureadv2/azureadv2.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								vendor/github.com/markbates/goth/providers/azureadv2/azureadv2.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | ||||
| package azureadv2 | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
|  | ||||
| // also https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints | ||||
| const ( | ||||
| 	authURLTemplate  string = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize" | ||||
| 	tokenURLTemplate string = "https://login.microsoftonline.com/%s/oauth2/v2.0/token" | ||||
| 	graphAPIResource string = "https://graph.microsoft.com/v1.0/" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	// TenantType are the well known tenant types to scope the users that can authenticate. TenantType is not an | ||||
| 	// exclusive list of Azure Tenants which can be used. A consumer can also use their own Tenant ID to scope | ||||
| 	// authentication to their specific Tenant either through the Tenant ID or the friendly domain name. | ||||
| 	// | ||||
| 	// see also https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints | ||||
| 	TenantType string | ||||
|  | ||||
| 	// Provider is the implementation of `goth.Provider` for accessing AzureAD V2. | ||||
| 	Provider struct { | ||||
| 		ClientKey    string | ||||
| 		Secret       string | ||||
| 		CallbackURL  string | ||||
| 		HTTPClient   *http.Client | ||||
| 		config       *oauth2.Config | ||||
| 		providerName string | ||||
| 	} | ||||
|  | ||||
| 	// ProviderOptions are the collection of optional configuration to provide when constructing a Provider | ||||
| 	ProviderOptions struct { | ||||
| 		Scopes []ScopeType | ||||
| 		Tenant TenantType | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| // These are the well known Azure AD Tenants. These are not an exclusive list of all Tenants | ||||
| // | ||||
| // See also https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints | ||||
| const ( | ||||
| 	// CommonTenant allows users with both personal Microsoft accounts and work/school accounts from Azure Active | ||||
| 	// Directory to sign into the application. | ||||
| 	CommonTenant TenantType = "common" | ||||
|  | ||||
| 	// OrganizationsTenant allows only users with work/school accounts from Azure Active Directory to sign into the application. | ||||
| 	OrganizationsTenant TenantType = "organizations" | ||||
|  | ||||
| 	// ConsumersTenant allows only users with personal Microsoft accounts (MSA) to sign into the application. | ||||
| 	ConsumersTenant TenantType = "consumers" | ||||
| ) | ||||
|  | ||||
| // New creates a new AzureAD provider, and sets up important connection details. | ||||
| // You should always call `AzureAD.New` to get a new Provider. Never try to create | ||||
| // one manually. | ||||
| func New(clientKey, secret, callbackURL string, opts ProviderOptions) *Provider { | ||||
| 	p := &Provider{ | ||||
| 		ClientKey:    clientKey, | ||||
| 		Secret:       secret, | ||||
| 		CallbackURL:  callbackURL, | ||||
| 		providerName: "azureadv2", | ||||
| 	} | ||||
|  | ||||
| 	p.config = newConfig(p, opts) | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| func newConfig(provider *Provider, opts ProviderOptions) *oauth2.Config { | ||||
| 	tenant := opts.Tenant | ||||
| 	if tenant == "" { | ||||
| 		tenant = CommonTenant | ||||
| 	} | ||||
|  | ||||
| 	c := &oauth2.Config{ | ||||
| 		ClientID:     provider.ClientKey, | ||||
| 		ClientSecret: provider.Secret, | ||||
| 		RedirectURL:  provider.CallbackURL, | ||||
| 		Endpoint: oauth2.Endpoint{ | ||||
| 			AuthURL:  fmt.Sprintf(authURLTemplate, tenant), | ||||
| 			TokenURL: fmt.Sprintf(tokenURLTemplate, tenant), | ||||
| 		}, | ||||
| 		Scopes: []string{}, | ||||
| 	} | ||||
|  | ||||
| 	if len(opts.Scopes) > 0 { | ||||
| 		c.Scopes = append(c.Scopes, scopesToStrings(opts.Scopes...)...) | ||||
| 	} else { | ||||
| 		defaultScopes := scopesToStrings(OpenIDScope, ProfileScope, EmailScope, UserReadScope) | ||||
| 		c.Scopes = append(c.Scopes, defaultScopes...) | ||||
| 	} | ||||
|  | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| // Name is the name used to retrieve this provider later. | ||||
| func (p *Provider) Name() string { | ||||
| 	return p.providerName | ||||
| } | ||||
|  | ||||
| // SetName is to update the name of the provider (needed in case of multiple providers of 1 type) | ||||
| func (p *Provider) SetName(name string) { | ||||
| 	p.providerName = name | ||||
| } | ||||
|  | ||||
| // Client is HTTP client to be used in all fetch operations. | ||||
| func (p *Provider) Client() *http.Client { | ||||
| 	return goth.HTTPClientWithFallBack(p.HTTPClient) | ||||
| } | ||||
|  | ||||
| // Debug is a no-op for the package | ||||
| func (p *Provider) Debug(debug bool) {} | ||||
|  | ||||
| // BeginAuth asks for an authentication end-point for AzureAD. | ||||
| func (p *Provider) BeginAuth(state string) (goth.Session, error) { | ||||
| 	authURL := p.config.AuthCodeURL(state) | ||||
|  | ||||
| 	return &Session{ | ||||
| 		AuthURL: authURL, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // FetchUser will go to AzureAD and access basic information about the user. | ||||
| func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { | ||||
| 	msSession := session.(*Session) | ||||
| 	user := goth.User{ | ||||
| 		AccessToken: msSession.AccessToken, | ||||
| 		Provider:    p.Name(), | ||||
| 		ExpiresAt:   msSession.ExpiresAt, | ||||
| 	} | ||||
|  | ||||
| 	if user.AccessToken == "" { | ||||
| 		return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName) | ||||
| 	} | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", graphAPIResource+"me", nil) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
| 	req.Header.Set(authorizationHeader(msSession)) | ||||
|  | ||||
| 	response, err := p.Client().Do(req) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
| 	defer response.Body.Close() | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode) | ||||
| 	} | ||||
|  | ||||
| 	err = userFromReader(response.Body, &user) | ||||
| 	user.AccessToken = msSession.AccessToken | ||||
| 	user.RefreshToken = msSession.RefreshToken | ||||
| 	user.ExpiresAt = msSession.ExpiresAt | ||||
| 	return user, err | ||||
| } | ||||
|  | ||||
| //RefreshTokenAvailable refresh token is provided by auth provider or not | ||||
| func (p *Provider) RefreshTokenAvailable() bool { | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| //RefreshToken get new access token based on the refresh token | ||||
| func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { | ||||
| 	token := &oauth2.Token{RefreshToken: refreshToken} | ||||
| 	ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token) | ||||
| 	newToken, err := ts.Token() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return newToken, err | ||||
| } | ||||
|  | ||||
| func authorizationHeader(session *Session) (string, string) { | ||||
| 	return "Authorization", fmt.Sprintf("Bearer %s", session.AccessToken) | ||||
| } | ||||
|  | ||||
| func userFromReader(r io.Reader, user *goth.User) error { | ||||
| 	u := struct { | ||||
| 		ID                string   `json:"id"`                // The unique identifier for the user. | ||||
| 		BusinessPhones    []string `json:"businessPhones"`    // The user's phone numbers. | ||||
| 		DisplayName       string   `json:"displayName"`       // The name displayed in the address book for the user. | ||||
| 		FirstName         string   `json:"givenName"`         // The first name of the user. | ||||
| 		JobTitle          string   `json:"jobTitle"`          // The user's job title. | ||||
| 		Email             string   `json:"mail"`              // The user's email address. | ||||
| 		MobilePhone       string   `json:"mobilePhone"`       // The user's cellphone number. | ||||
| 		OfficeLocation    string   `json:"officeLocation"`    // The user's physical office location. | ||||
| 		PreferredLanguage string   `json:"preferredLanguage"` // The user's language of preference. | ||||
| 		LastName          string   `json:"surname"`           // The last name of the user. | ||||
| 		UserPrincipalName string   `json:"userPrincipalName"` // The user's principal name. | ||||
| 	}{} | ||||
|  | ||||
| 	userBytes, err := ioutil.ReadAll(r) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := json.Unmarshal(userBytes, &u); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	user.Email = u.Email | ||||
| 	user.Name = u.DisplayName | ||||
| 	user.FirstName = u.FirstName | ||||
| 	user.LastName = u.LastName | ||||
| 	user.NickName = u.DisplayName | ||||
| 	user.Location = u.OfficeLocation | ||||
| 	user.UserID = u.ID | ||||
| 	user.AvatarURL = graphAPIResource + fmt.Sprintf("users/%s/photo/$value", u.ID) | ||||
| 	// Make sure all of the information returned is available via RawData | ||||
| 	if err := json.Unmarshal(userBytes, &user.RawData); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func scopesToStrings(scopes ...ScopeType) []string { | ||||
| 	strs := make([]string, len(scopes)) | ||||
| 	for i := 0; i < len(scopes); i++ { | ||||
| 		strs[i] = string(scopes[i]) | ||||
| 	} | ||||
| 	return strs | ||||
| } | ||||
							
								
								
									
										714
									
								
								vendor/github.com/markbates/goth/providers/azureadv2/scopes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										714
									
								
								vendor/github.com/markbates/goth/providers/azureadv2/scopes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,714 @@ | ||||
| package azureadv2 | ||||
|  | ||||
| type ( | ||||
| 	// ScopeType are the well known scopes which can be requested | ||||
| 	ScopeType string | ||||
| ) | ||||
|  | ||||
| // OpenID Permissions | ||||
| // | ||||
| // You can use these permissions to specify artifacts that you want returned in Azure AD authorization and token | ||||
| // requests. They are supported differently by the Azure AD v1.0 and v2.0 endpoints. | ||||
| // | ||||
| // With the Azure AD (v1.0) endpoint, only the openid permission is used. You specify it in the scope parameter in an | ||||
| // authorization request to return an ID token when you use the OpenID Connect protocol to sign in a user to your app. | ||||
| // For more information, see Authorize access to web applications using OpenID Connect and Azure Active Directory. To | ||||
| // successfully return an ID token, you must also make sure that the User.Read permission is configured when you | ||||
| // register your app. | ||||
| // | ||||
| // With the Azure AD v2.0 endpoint, you specify the offline_access permission in the scope parameter to explicitly | ||||
| // request a refresh token when using the OAuth 2.0 or OpenID Connect protocols. With OpenID Connect, you specify the | ||||
| // openid permission to request an ID token. You can also specify the email permission, profile permission, or both to | ||||
| // return additional claims in the ID token. You do not need to specify User.Read to return an ID token with the v2.0 | ||||
| // endpoint. For more information, see OpenID Connect scopes. | ||||
| const ( | ||||
| 	// OpenIDScope shows on the work account consent page as the "Sign you in" permission, and on the personal Microsoft | ||||
| 	// account consent page as the "View your profile and connect to apps and services using your Microsoft account" | ||||
| 	// permission. With this permission, an app can receive a unique identifier for the user in the form of the sub | ||||
| 	// claim. It also gives the app access to the UserInfo endpoint. The openid scope can be used at the v2.0 token | ||||
| 	// endpoint to acquire ID tokens, which can be used to secure HTTP calls between different components of an app. | ||||
| 	OpenIDScope ScopeType = "openid" | ||||
|  | ||||
| 	// EmailScope can be used with the openid scope and any others. It gives the app access to the user's primary | ||||
| 	// email address in the form of the email claim. The email claim is included in a token only if an email address is | ||||
| 	// associated with the user account, which is not always the case. If it uses the email scope, your app should be | ||||
| 	// prepared to handle a case in which the email claim does not exist in the token. | ||||
| 	EmailScope ScopeType = "email" | ||||
|  | ||||
| 	// ProfileScope can be used with the openid scope and any others. It gives the app access to a substantial | ||||
| 	// amount of information about the user. The information it can access includes, but is not limited to, the user's | ||||
| 	// given name, surname, preferred username, and object ID. For a complete list of the profile claims available in | ||||
| 	// the id_tokens parameter for a specific user, see the v2.0 tokens reference: | ||||
| 	// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-id-and-access-tokens. | ||||
| 	ProfileScope ScopeType = "profile" | ||||
|  | ||||
| 	// OfflineAccessScope gives your app access to resources on behalf of the user for an extended time. On the work | ||||
| 	// account consent page, this scope appears as the "Access your data anytime" permission. On the personal Microsoft | ||||
| 	// account consent page, it appears as the "Access your info anytime" permission. When a user approves the | ||||
| 	// offline_access scope, your app can receive refresh tokens from the v2.0 token endpoint. Refresh tokens are | ||||
| 	// long-lived. Your app can get new access tokens as older ones expire. | ||||
| 	// | ||||
| 	// If your app does not request the offline_access scope, it won't receive refresh tokens. This means that when you | ||||
| 	// redeem an authorization code in the OAuth 2.0 authorization code flow, you'll receive only an access token from | ||||
| 	// the /token endpoint. The access token is valid for a short time. The access token usually expires in one hour. | ||||
| 	// At that point, your app needs to redirect the user back to the /authorize endpoint to get a new authorization | ||||
| 	// code. During this redirect, depending on the type of app, the user might need to enter their credentials again | ||||
| 	// or consent again to permissions. | ||||
| 	OfflineAccessScope ScopeType = "offline_access" | ||||
| ) | ||||
|  | ||||
| // Calendar Permissions | ||||
| // | ||||
| // Calendars.Read.Shared and Calendars.ReadWrite.Shared are only valid for work or school accounts. All other | ||||
| // permissions are valid for both Microsoft accounts and work or school accounts. | ||||
| // | ||||
| // See also https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference | ||||
| const ( | ||||
| 	// CalendarsReadScope allows the app to read events in user calendars. | ||||
| 	CalendarsReadScope ScopeType = "Calendars.Read" | ||||
|  | ||||
| 	// CalendarsReadSharedScope allows the app to read events in all calendars that the user can access, including | ||||
| 	// delegate and shared calendars. | ||||
| 	CalendarsReadSharedScope ScopeType = "Calendars.Read.Shared" | ||||
|  | ||||
| 	// CalendarsReadWriteScope allows the app to create, read, update, and delete events in user calendars. | ||||
| 	CalendarsReadWriteScope ScopeType = "Calendars.ReadWrite" | ||||
|  | ||||
| 	// CalendarsReadWriteSharedScope allows the app to create, read, update and delete events in all calendars the user | ||||
| 	// has permissions to access. This includes delegate and shared calendars. | ||||
| 	CalendarsReadWriteSharedScope ScopeType = "Calendars.ReadWrite.Shared" | ||||
| ) | ||||
|  | ||||
| // Contacts Permissions | ||||
| // | ||||
| // Only the Contacts.Read and Contacts.ReadWrite delegated permissions are valid for Microsoft accounts. | ||||
| // | ||||
| // See also https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference | ||||
| const ( | ||||
| 	// ContactsReadScope allows the app to read contacts that the user has permissions to access, including the user's | ||||
| 	// own and shared contacts. | ||||
| 	ContactsReadScope ScopeType = "Contacts.Read" | ||||
|  | ||||
| 	// ContactsReadSharedScope allows the app to read contacts that the user has permissions to access, including the | ||||
| 	// user's own and shared contacts. | ||||
| 	ContactsReadSharedScope ScopeType = "Contacts.Read.Shared" | ||||
|  | ||||
| 	// ContactsReadWriteScope allows the app to create, read, update, and delete user contacts. | ||||
| 	ContactsReadWriteScope ScopeType = "Contacts.ReadWrite" | ||||
|  | ||||
| 	// ContactsReadWriteSharedScope allows the app to create, read, update and delete contacts that the user has | ||||
| 	// permissions to, including the user's own and shared contacts. | ||||
| 	ContactsReadWriteSharedScope ScopeType = "Contacts.ReadWrite.Shared" | ||||
| ) | ||||
|  | ||||
| // Device Permissions | ||||
| // | ||||
| // The Device.Read and Device.Command delegated permissions are valid only for personal Microsoft accounts. | ||||
| // | ||||
| // See also https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference | ||||
| const ( | ||||
| 	// DeviceReadScope allows the app to read a user's list of devices on behalf of the signed-in user. | ||||
| 	DeviceReadScope ScopeType = "Device.Read" | ||||
|  | ||||
| 	// DeviceCommandScope allows the app to launch another app or communicate with another app on a user's device on | ||||
| 	// behalf of the signed-in user. | ||||
| 	DeviceCommandScope ScopeType = "Device.Command" | ||||
| ) | ||||
|  | ||||
| // Directory Permissions | ||||
| // | ||||
| // Directory permissions are not supported on Microsoft accounts. | ||||
| // | ||||
| // Directory permissions provide the highest level of privilege for accessing directory resources such as User, Group, | ||||
| // and Device in an organization. | ||||
| // | ||||
| // They also exclusively control access to other directory resources like: organizational contacts, schema extension | ||||
| // APIs, Privileged Identity Management (PIM) APIs, as well as many of the resources and APIs listed under the Azure | ||||
| // Active Directory node in the v1.0 and beta API reference documentation. These include administrative units, directory | ||||
| // roles, directory settings, policy, and many more. | ||||
| // | ||||
| // The Directory.ReadWrite.All permission grants the following privileges: | ||||
| //  - Full read of all directory resources (both declared properties and navigation properties) | ||||
| //  - Create and update users | ||||
| //  - Disable and enable users (but not company administrator) | ||||
| //  - Set user alternative security id (but not administrators) | ||||
| //  - Create and update groups | ||||
| //  - Manage group memberships | ||||
| //  - Update group owner | ||||
| //  - Manage license assignments | ||||
| //  - Define schema extensions on applications | ||||
| //  - Note: No rights to reset user passwords | ||||
| //  - Note: No rights to delete resources (including users or groups) | ||||
| //  - Note: Specifically excludes create or update for resources not listed above. This includes: application, | ||||
| //    oAauth2Permissiongrant, appRoleAssignment, device, servicePrincipal, organization, domains, and so on. | ||||
| // | ||||
| // See also https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference | ||||
| const ( | ||||
| 	// DirectoryReadAllScope allows the app to read data in your organization's directory, such as users, groups and | ||||
| 	// apps. | ||||
| 	// | ||||
| 	// Note: Users may consent to applications that require this permission if the application is registered in their | ||||
| 	// own organization’s tenant. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DirectoryReadAllScope ScopeType = "Directory.Read.All" | ||||
|  | ||||
| 	// DirectoryReadWriteAllScope allows the app to read and write data in your organization's directory, such as users, | ||||
| 	// and groups. It does not allow the app to delete users or groups, or reset user passwords. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DirectoryReadWriteAllScope ScopeType = "Directory.ReadWrite.All" | ||||
|  | ||||
| 	// DirectoryAccessAsUserAllScope allows the app to have the same access to information in the directory as the | ||||
| 	// signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DirectoryAccessAsUserAllScope ScopeType = "Directory.AccessAsUser.All" | ||||
| ) | ||||
|  | ||||
| // Education Administration Permissions | ||||
| const ( | ||||
| 	// EduAdministrationReadScope allows the app to read education app settings on behalf of the user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	EduAdministrationReadScope ScopeType = "EduAdministration.Read" | ||||
|  | ||||
| 	// EduAdministrationReadWriteScope allows the app to manage education app settings on behalf of the user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	EduAdministrationReadWriteScope ScopeType = "EduAdministration.ReadWrite" | ||||
|  | ||||
| 	// EduAssignmentsReadBasicScope allows the app to read assignments without grades on behalf of the user | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	EduAssignmentsReadBasicScope ScopeType = "EduAssignments.ReadBasic" | ||||
|  | ||||
| 	// EduAssignmentsReadWriteBasicScope allows the app to read and write assignments without grades on behalf of the | ||||
| 	// user | ||||
| 	EduAssignmentsReadWriteBasicScope ScopeType = "EduAssignments.ReadWriteBasic" | ||||
|  | ||||
| 	// EduAssignmentsReadScope allows the app to read assignments and their grades on behalf of the user | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	EduAssignmentsReadScope ScopeType = "EduAssignments.Read" | ||||
|  | ||||
| 	// EduAssignmentsReadWriteScope allows the app to read and write assignments and their grades on behalf of the user | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	EduAssignmentsReadWriteScope ScopeType = "EduAssignments.ReadWrite" | ||||
|  | ||||
| 	// EduRosteringReadBasicScope allows the app to read a limited subset of the data from the  structure of schools and | ||||
| 	// classes in an organization's roster and  education-specific information about users to be read on behalf of the | ||||
| 	// user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	EduRosteringReadBasicScope ScopeType = "EduRostering.ReadBasic" | ||||
| ) | ||||
|  | ||||
| // Files Permissions | ||||
| // | ||||
| // The Files.Read, Files.ReadWrite, Files.Read.All, and Files.ReadWrite.All delegated permissions are valid on both | ||||
| // personal Microsoft accounts and work or school accounts. Note that for personal accounts, Files.Read and | ||||
| // Files.ReadWrite also grant access to files shared with the signed-in user. | ||||
| // | ||||
| // The Files.Read.Selected and Files.ReadWrite.Selected delegated permissions are only valid on work or school accounts | ||||
| // and are only exposed for working with Office 365 file handlers (v1.0) | ||||
| // https://msdn.microsoft.com/office/office365/howto/using-cross-suite-apps. They should not be used for directly | ||||
| // calling Microsoft Graph APIs. | ||||
| // | ||||
| // The Files.ReadWrite.AppFolder delegated permission is only valid for personal accounts and is used for accessing the | ||||
| // App Root special folder https://dev.onedrive.com/misc/appfolder.htm with the OneDrive Get special folder | ||||
| // https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/drive_get_specialfolder Microsoft Graph API. | ||||
| const ( | ||||
| 	// FilesReadScope allows the app to read the signed-in user's files. | ||||
| 	FilesReadScope ScopeType = "Files.Read" | ||||
|  | ||||
| 	// FilesReadAllScope allows the app to read all files the signed-in user can access. | ||||
| 	FilesReadAllScope ScopeType = "Files.Read.All" | ||||
|  | ||||
| 	// FilesReadWrite allows the app to read, create, update, and delete the signed-in user's files. | ||||
| 	FilesReadWriteScope ScopeType = "Files.ReadWrite" | ||||
|  | ||||
| 	// FilesReadWriteAllScope allows the app to read, create, update, and delete all files the signed-in user can access. | ||||
| 	FilesReadWriteAllScope ScopeType = "Files.ReadWrite.All" | ||||
|  | ||||
| 	// FilesReadWriteAppFolderScope allows the app to read, create, update, and delete files in the application's folder. | ||||
| 	FilesReadWriteAppFolderScope ScopeType = "Files.ReadWrite.AppFolder" | ||||
|  | ||||
| 	// FilesReadSelectedScope allows the app to read files that the user selects. The app has access for several hours | ||||
| 	// after the user selects a file. | ||||
| 	// | ||||
| 	// preview | ||||
| 	FilesReadSelectedScope ScopeType = "Files.Read.Selected" | ||||
|  | ||||
| 	// FilesReadWriteSelectedScope allows the app to read and write files that the user selects. The app has access for | ||||
| 	// several hours after the user selects a file | ||||
| 	// | ||||
| 	// preview | ||||
| 	FilesReadWriteSelectedScope ScopeType = "Files.ReadWrite.Selected" | ||||
| ) | ||||
|  | ||||
| // Group Permissions | ||||
| // | ||||
| // Group functionality is not supported on personal Microsoft accounts. | ||||
| // | ||||
| // For Office 365 groups, Group permissions grant the app access to the contents of the group; for example, | ||||
| // conversations, files, notes, and so on. | ||||
| // | ||||
| // For application permissions, there are some limitations for the APIs that are supported. For more information, see | ||||
| // known issues. | ||||
| // | ||||
| // In some cases, an app may need Directory permissions to read some group properties like member and memberOf. For | ||||
| // example, if a group has a one or more servicePrincipals as members, the app will need effective permissions to read | ||||
| // service principals through being granted one of the Directory.* permissions, otherwise Microsoft Graph will return an | ||||
| // error. (In the case of delegated permissions, the signed-in user will also need sufficient privileges in the | ||||
| // organization to read service principals.) The same guidance applies for the memberOf property, which can return | ||||
| // administrativeUnits. | ||||
| // | ||||
| // Group permissions are also used to control access to Microsoft Planner resources and APIs. Only delegated permissions | ||||
| // are supported for Microsoft Planner APIs; application permissions are not supported. Personal Microsoft accounts are | ||||
| // not supported. | ||||
| const ( | ||||
| 	// GroupReadAllScope allows the app to list groups, and to read their properties and all group memberships on behalf | ||||
| 	// of the signed-in user.  Also allows the app to read calendar, conversations, files, and other group content for | ||||
| 	// all groups the signed-in user can access. | ||||
| 	GroupReadAllScope ScopeType = "Group.Read.All" | ||||
|  | ||||
| 	// GroupReadWriteAllScope allows the app to create groups and read all group properties and memberships on behalf of | ||||
| 	// the signed-in user.  Additionally allows group owners to manage their groups and allows group members to update | ||||
| 	// group content. | ||||
| 	GroupReadWriteAllScope ScopeType = "Group.ReadWrite.All" | ||||
| ) | ||||
|  | ||||
| // Identity Risk Event Permissions | ||||
| // | ||||
| // IdentityRiskEvent.Read.All is valid only for work or school accounts. For an app with delegated permissions to read | ||||
| // identity risk information, the signed-in user must be a member of one of the following administrator roles: Global | ||||
| // Administrator, Security Administrator, or Security Reader. For more information about administrator roles, see | ||||
| // Assigning administrator roles in Azure Active Directory. | ||||
| const ( | ||||
| 	// IdentityRiskEventReadAllScope allows the app to read identity risk event information for all users in your | ||||
| 	// organization on behalf of the signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	IdentityRiskEventReadAllScope ScopeType = "IdentityRiskEvent.Read.All" | ||||
| ) | ||||
|  | ||||
| // Identity Provider Permissions | ||||
| // | ||||
| // IdentityProvider.Read.All and IdentityProvider.ReadWrite.All are valid only for work or school accounts. For an app | ||||
| // to read or write identity providers with delegated permissions, the signed-in user must be assigned the Global | ||||
| // Administrator role. For more information about administrator roles, see Assigning administrator roles in Azure Active | ||||
| // Directory. | ||||
| const ( | ||||
| 	// IdentityProviderReadAllScope allows the app to read identity providers configured in your Azure AD or Azure AD | ||||
| 	// B2C tenant on behalf of the signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	IdentityProviderReadAllScope ScopeType = "IdentityProvider.Read.All" | ||||
|  | ||||
| 	// IdentityProviderReadWriteAllScope allows the app to read or write identity providers configured in your Azure AD | ||||
| 	// or Azure AD B2C tenant on behalf of the signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	IdentityProviderReadWriteAllScope ScopeType = "IdentityProvider.ReadWrite.All" | ||||
| ) | ||||
|  | ||||
| // Device Management Permissions | ||||
| // | ||||
| // Using the Microsoft Graph APIs to configure Intune controls and policies still requires that the Intune service is | ||||
| // correctly licensed by the customer. | ||||
| // | ||||
| // These permissions are only valid for work or school accounts. | ||||
| const ( | ||||
| 	// DeviceManagementAppsReadAllScope allows the app to read the properties, group assignments and status of apps, app | ||||
| 	// configurations and app protection policies managed by Microsoft Intune. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementAppsReadAllScope ScopeType = "DeviceManagementApps.Read.All" | ||||
|  | ||||
| 	// DeviceManagementAppsReadWriteAllScope allows the app to read and write the properties, group assignments and | ||||
| 	// status of apps, app configurations and app protection policies managed by Microsoft Intune. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementAppsReadWriteAllScope ScopeType = "DeviceManagementApps.ReadWrite.All" | ||||
|  | ||||
| 	// DeviceManagementConfigurationReadAllScope allows the app to read properties of Microsoft Intune-managed device | ||||
| 	// configuration and device compliance policies and their assignment to groups. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementConfigurationReadAllScope ScopeType = "DeviceManagementConfiguration.Read.All" | ||||
|  | ||||
| 	// DeviceManagementConfigurationReadWriteAllScope allows the app to read and write properties of Microsoft | ||||
| 	// Intune-managed device configuration and device compliance policies and their assignment to groups. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementConfigurationReadWriteAllScope ScopeType = "DeviceManagementConfiguration.ReadWrite.All" | ||||
|  | ||||
| 	// DeviceManagementManagedDevicesPrivilegedOperationsAllScope allows the app to perform remote high impact actions | ||||
| 	// such as wiping the device or resetting the passcode on devices managed by Microsoft Intune. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementManagedDevicesPrivilegedOperationsAllScope ScopeType = "DeviceManagementManagedDevices.PrivilegedOperations.All" | ||||
|  | ||||
| 	// DeviceManagementManagedDevicesReadAllScope allows the app to read the properties of devices managed by Microsoft | ||||
| 	// Intune. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementManagedDevicesReadAllScope ScopeType = "DeviceManagementManagedDevices.Read.All" | ||||
|  | ||||
| 	// DeviceManagementManagedDevicesReadWriteAllScope allows the app to read and write the properties of devices | ||||
| 	// managed by Microsoft Intune. Does not allow high impact operations such as remote wipe and password reset on the | ||||
| 	// device’s owner. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementManagedDevicesReadWriteAllScope ScopeType = "DeviceManagementManagedDevices.ReadWrite.All" | ||||
|  | ||||
| 	// DeviceManagementRBACReadAllScope allows the app to read the properties relating to the Microsoft Intune | ||||
| 	// Role-Based Access Control (RBAC) settings. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementRBACReadAllScope ScopeType = "DeviceManagementRBAC.Read.All" | ||||
|  | ||||
| 	// DeviceManagementRBACReadWriteAllScope allows the app to read and write the properties relating to the Microsoft | ||||
| 	// Intune Role-Based Access Control (RBAC) settings. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementRBACReadWriteAllScope ScopeType = "DeviceManagementRBAC.ReadWrite.All" | ||||
|  | ||||
| 	// DeviceManagementServiceConfigReadAllScope allows the app to read Intune service properties including device | ||||
| 	// enrollment and third party service connection configuration. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementServiceConfigReadAllScope ScopeType = "DeviceManagementServiceConfig.Read.All" | ||||
|  | ||||
| 	// DeviceManagementServiceConfigReadWriteAllScope allows the app to read and write Microsoft Intune service | ||||
| 	// properties including device enrollment and third party service connection configuration. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	DeviceManagementServiceConfigReadWriteAllScope ScopeType = "DeviceManagementServiceConfig.ReadWrite.All" | ||||
| ) | ||||
|  | ||||
| // Mail Permissions | ||||
| // | ||||
| // Mail.Read.Shared, Mail.ReadWrite.Shared, and Mail.Send.Shared are only valid for work or school accounts. All other | ||||
| // permissions are valid for both Microsoft accounts and work or school accounts. | ||||
| // | ||||
| // With the Mail.Send or Mail.Send.Shared permission, an app can send mail and save a copy to the user's Sent Items | ||||
| // folder, even if the app does not use a corresponding Mail.ReadWrite or Mail.ReadWrite.Shared permission. | ||||
| const ( | ||||
| 	// MailReadScope allows the app to read email in user mailboxes. | ||||
| 	MailReadScope ScopeType = "Mail.Read" | ||||
|  | ||||
| 	// MailReadWriteScope allows the app to create, read, update, and delete email in user mailboxes. Does not include | ||||
| 	// permission to send mail. | ||||
| 	MailReadWriteScope ScopeType = "Mail.ReadWrite" | ||||
|  | ||||
| 	// MailReadSharedScope allows the app to read mail that the user can access, including the user's own and shared | ||||
| 	// mail. | ||||
| 	MailReadSharedScope ScopeType = "Mail.Read.Shared" | ||||
|  | ||||
| 	// MailReadWriteSharedScope allows the app to create, read, update, and delete mail that the user has permission to | ||||
| 	// access, including the user's own and shared mail. Does not include permission to send mail. | ||||
| 	MailReadWriteSharedScope ScopeType = "Mail.ReadWrite.Shared" | ||||
|  | ||||
| 	// MailSend allowsScope the app to send mail as users in the organization. | ||||
| 	MailSendScope ScopeType = "Mail.Send" | ||||
|  | ||||
| 	// MailSendSharedScope allows the app to send mail as the signed-in user, including sending on-behalf of others. | ||||
| 	MailSendSharedScope ScopeType = "Mail.Send.Shared" | ||||
|  | ||||
| 	// MailboxSettingsReadScope allows the app to the read user's mailbox settings. Does not include permission to send | ||||
| 	// mail. | ||||
| 	MailboxSettingsReadScope ScopeType = "Mailbox.Settings.Read" | ||||
|  | ||||
| 	// MailboxSettingsReadWriteScope allows the app to create, read, update, and delete user's mailbox settings. Does | ||||
| 	// not include permission to directly send mail, but allows the app to create rules that can forward or redirect | ||||
| 	// messages. | ||||
| 	MailboxSettingsReadWriteScope ScopeType = "MailboxSettings.ReadWrite" | ||||
| ) | ||||
|  | ||||
| // Member Permissions | ||||
| // | ||||
| // Member.Read.Hidden is valid only on work or school accounts. | ||||
| // | ||||
| // Membership in some Office 365 groups can be hidden. This means that only the members of the group can view its | ||||
| // members. This feature can be used to help comply with regulations that require an organization to hide group | ||||
| // membership from outsiders (for example, an Office 365 group that represents students enrolled in a class). | ||||
| const ( | ||||
| 	// MemberReadHiddenScope allows the app to read the memberships of hidden groups and administrative units on behalf | ||||
| 	// of the signed-in user, for those hidden groups and administrative units that the signed-in user has access to. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	MemberReadHiddenScope ScopeType = "Member.Read.Hidden" | ||||
| ) | ||||
|  | ||||
| // Notes Permissions | ||||
| // | ||||
| // Notes.Read.All and Notes.ReadWrite.All are only valid for work or school accounts. All other permissions are valid | ||||
| // for both Microsoft accounts and work or school accounts. | ||||
| // | ||||
| // With the Notes.Create permission, an app can view the OneNote notebook hierarchy of the signed-in user and create | ||||
| // OneNote content (notebooks, section groups, sections, pages, etc.). | ||||
| // | ||||
| // Notes.ReadWrite and Notes.ReadWrite.All also allow the app to modify the permissions on the OneNote content that can | ||||
| // be accessed by the signed-in user. | ||||
| // | ||||
| // For work or school accounts, Notes.Read.All and Notes.ReadWrite.All allow the app to access other users' OneNote | ||||
| // content that the signed-in user has permission to within the organization. | ||||
| const ( | ||||
| 	// NotesReadScope allows the app to read OneNote notebooks on behalf of the signed-in user. | ||||
| 	NotesReadScope ScopeType = "Notes.Read" | ||||
|  | ||||
| 	// NotesCreateScope allows the app to read the titles of OneNote notebooks and sections and to create new pages, | ||||
| 	// notebooks, and sections on behalf of the signed-in user. | ||||
| 	NotesCreateScope ScopeType = "Notes.Create" | ||||
|  | ||||
| 	// NotesReadWriteScope allows the app to read, share, and modify OneNote notebooks on behalf of the signed-in user. | ||||
| 	NotesReadWriteScope ScopeType = "Notes.ReadWrite" | ||||
|  | ||||
| 	// NotesReadAllScope allows the app to read OneNote notebooks that the signed-in user has access to in the | ||||
| 	// organization. | ||||
| 	NotesReadAllScope ScopeType = "Notes.Read.All" | ||||
|  | ||||
| 	// NotesReadWriteAllScope allows the app to read, share, and modify OneNote notebooks that the signed-in user has | ||||
| 	// access to in the organization. | ||||
| 	NotesReadWriteAllScope ScopeType = "Notes.ReadWrite.All" | ||||
| ) | ||||
|  | ||||
| // People Permissions | ||||
| // | ||||
| // The People.Read.All permission is only valid for work and school accounts. | ||||
| const ( | ||||
| 	// PeopleReadScope allows the app to read a scored list of people relevant to the signed-in user. The list can | ||||
| 	// include local contacts, contacts from social networking or your organization's directory, and people from recent | ||||
| 	// communications (such as email and Skype). | ||||
| 	PeopleReadScope ScopeType = "People.Read" | ||||
|  | ||||
| 	// PeopleReadAllScope allows the app to read a scored list of people relevant to the signed-in user or other users | ||||
| 	// in the signed-in user's organization. The list can include local contacts, contacts from social networking or | ||||
| 	// your organization's directory, and people from recent communications (such as email and Skype). Also allows the | ||||
| 	// app to search the entire directory of the signed-in user's organization. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	PeopleReadAllScope ScopeType = "People.Read.All" | ||||
| ) | ||||
|  | ||||
| // Report Permissions | ||||
| // | ||||
| // Reports permissions are only valid for work or school accounts. | ||||
| const ( | ||||
| 	// ReportsReadAllScope allows an app to read all service usage reports without a signed-in user. Services that | ||||
| 	// provide usage reports include Office 365 and Azure Active Directory. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	ReportsReadAllScope ScopeType = "Reports.Read.All" | ||||
| ) | ||||
|  | ||||
| // Security Permissions | ||||
| // | ||||
| // Security permissions are valid only on work or school accounts. | ||||
| const ( | ||||
| 	// SecurityEventsReadAllScope allows the app to read your organization’s security events on behalf of the signed-in | ||||
| 	// user. | ||||
| 	// requires admin consent | ||||
| 	SecurityEventsReadAllScope ScopeType = "SecurityEvents.Read.All" | ||||
|  | ||||
| 	// SecurityEventsReadWriteAllScope allows the app to read your organization’s security events on behalf of the | ||||
| 	// signed-in user. Also allows the app to update editable properties in security events on behalf of the signed-in | ||||
| 	// user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	SecurityEventsReadWriteAllScope ScopeType = "SecurityEvents.ReadWrite.All" | ||||
| ) | ||||
|  | ||||
| // Sites Permissions | ||||
| // | ||||
| // Sites permissions are valid only on work or school accounts. | ||||
| const ( | ||||
| 	// SitesReadAllScope allows the app to read documents and list items in all site collections on behalf of the | ||||
| 	// signed-in user. | ||||
| 	SitesReadAllScope ScopeType = "Sites.Read.All" | ||||
|  | ||||
| 	// SitesReadWriteAllScope allows the app to edit or delete documents and list items in all site collections on | ||||
| 	// behalf of the signed-in user. | ||||
| 	SitesReadWriteAllScope ScopeType = "Sites.ReadWrite.All" | ||||
|  | ||||
| 	// SitesManageAllScope allows the app to manage and create lists, documents, and list items in all site collections | ||||
| 	// on behalf of the signed-in user. | ||||
| 	SitesManageAllScope ScopeType = "Sites.Manage.All" | ||||
|  | ||||
| 	// SitesFullControlAllScope allows the app to have full control to SharePoint sites in all site collections on | ||||
| 	// behalf of the signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	SitesFullControlAllScope ScopeType = "Sites.FullControl.All" | ||||
| ) | ||||
|  | ||||
| // Tasks Permissions | ||||
| // | ||||
| // Tasks permissions are used to control access for Outlook tasks. Access for Microsoft Planner tasks is controlled by | ||||
| // Group permissions. | ||||
| // | ||||
| // Shared permissions are currently only supported for work or school accounts. Even with Shared permissions, reads and | ||||
| // writes may fail if the user who owns the shared content has not granted the accessing user permissions to modify | ||||
| // content within the folder. | ||||
| const ( | ||||
| 	// TasksReadScope allows the app to read user tasks. | ||||
| 	TasksReadScope ScopeType = "Tasks.Read" | ||||
|  | ||||
| 	// TasksReadSharedScope allows the app to read tasks a user has permissions to access, including their own and | ||||
| 	// shared tasks. | ||||
| 	TasksReadSharedScope ScopeType = "Tasks.Read.Shared" | ||||
|  | ||||
| 	// TasksReadWriteScope allows the app to create, read, update and delete tasks and containers (and tasks in them) | ||||
| 	// that are assigned to or shared with the signed-in user. | ||||
| 	TasksReadWriteScope ScopeType = "Tasks.ReadWrite" | ||||
|  | ||||
| 	// TasksReadWriteSharedScope allows the app to create, read, update, and delete tasks a user has permissions to, | ||||
| 	// including their own and shared tasks. | ||||
| 	TasksReadWriteSharedScope ScopeType = "Tasks.ReadWrite.Shared" | ||||
| ) | ||||
|  | ||||
| // Terms of Use Permissions | ||||
| // | ||||
| // All the permissions above are valid only for work or school accounts. | ||||
| // | ||||
| // For an app to read or write all agreements or agreement acceptances with delegated permissions, the signed-in user | ||||
| // must be assigned the Global Administrator, Conditional Access Administrator or Security Administrator role. For more | ||||
| // information about administrator roles, see Assigning administrator roles in Azure Active Directory | ||||
| // https://docs.microsoft.com/azure/active-directory/active-directory-assign-admin-roles. | ||||
| const ( | ||||
| 	// AgreementReadAllScope allows the app to read terms of use agreements on behalf of the signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	AgreementReadAllScope ScopeType = "Agreement.Read.All" | ||||
|  | ||||
| 	// AgreementReadWriteAllScope allows the app to read and write terms of use agreements on behalf of the signed-in | ||||
| 	// user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	AgreementReadWriteAllScope ScopeType = "Agreement.ReadWrite.All" | ||||
|  | ||||
| 	// AgreementAcceptanceReadScope allows the app to read terms of use acceptance statuses on behalf of the signed-in | ||||
| 	// user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	AgreementAcceptanceReadScope ScopeType = "AgreementAcceptance.Read" | ||||
|  | ||||
| 	// AgreementAcceptanceReadAllScope allows the app to read terms of use acceptance statuses on behalf of the | ||||
| 	// signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	AgreementAcceptanceReadAllScope ScopeType = "AgreementAcceptance.Read.All" | ||||
| ) | ||||
|  | ||||
| // User Permissions | ||||
| // | ||||
| // The only permissions valid for Microsoft accounts are User.Read and User.ReadWrite. For work or school accounts, all | ||||
| // permissions are valid. | ||||
| // | ||||
| // With the User.Read permission, an app can also read the basic company information of the signed-in user for a work or | ||||
| // school account through the organization resource. The following properties are available: id, displayName, and | ||||
| // verifiedDomains. | ||||
| // | ||||
| // For work or school accounts, the full profile includes all of the declared properties of the User resource. On reads, | ||||
| // only a limited number of properties are returned by default. To read properties that are not in the default set, use | ||||
| // $select. The default properties are: | ||||
| //  displayName | ||||
| //  givenName | ||||
| //  jobTitle | ||||
| //  mail | ||||
| //  mobilePhone | ||||
| //  officeLocation | ||||
| //  preferredLanguage | ||||
| //  surname | ||||
| //  userPrincipalName | ||||
| // | ||||
| // User.ReadWrite and User.Readwrite.All delegated permissions allow the app to update the following profile properties | ||||
| // for work or school accounts: | ||||
| //  aboutMe | ||||
| //  birthday | ||||
| //  hireDate | ||||
| //  interests | ||||
| //  mobilePhone | ||||
| //  mySite | ||||
| //  pastProjects | ||||
| //  photo | ||||
| //  preferredName | ||||
| //  responsibilities | ||||
| //  schools | ||||
| //  skills | ||||
| // | ||||
| // With the User.ReadWrite.All application permission, the app can update all of the declared properties of work or | ||||
| // school accounts except for password. | ||||
| // | ||||
| // To read or write direct reports (directReports) or the manager (manager) of a work or school account, the app must | ||||
| // have either User.Read.All (read only) or User.ReadWrite.All. | ||||
| // | ||||
| // The User.ReadBasic.All permission constrains app access to a limited set of properties known as the basic profile. | ||||
| // This is because the full profile might contain sensitive directory information. The basic profile includes only the | ||||
| // following properties: | ||||
| //  displayName | ||||
| //  givenName | ||||
| //  mail | ||||
| //  photo | ||||
| //  surname | ||||
| //  userPrincipalName | ||||
| // | ||||
| // To read the group memberships of a user (memberOf), the app must have either Group.Read.All or Group.ReadWrite.All. | ||||
| // However, if the user also has membership in a directoryRole or an administrativeUnit, the app will need effective | ||||
| // permissions to read those resources too, or Microsoft Graph will return an error. This means the app will also need | ||||
| // Directory permissions, and, for delegated permissions, the signed-in user will also need sufficient privileges in the | ||||
| // organization to access directory roles and administrative units. | ||||
| const ( | ||||
| 	// UserReadScope allows users to sign-in to the app, and allows the app to read the profile of signed-in users. It | ||||
| 	// also allows the app to read basic company information of signed-in users. | ||||
| 	UserReadScope ScopeType = "User.Read" | ||||
|  | ||||
| 	// UserReadWriteScope allows the app to read the signed-in user's full profile. It also allows the app to update the | ||||
| 	// signed-in user's profile information on their behalf. | ||||
| 	UserReadWriteScope ScopeType = "User.ReadWrite" | ||||
|  | ||||
| 	// UserReadBasicAllScope allows the app to read a basic set of profile properties of other users in your | ||||
| 	// organization on behalf of the signed-in user. This includes display name, first and last name, email address, | ||||
| 	// open extensions and photo. Also allows the app to read the full profile of the signed-in user. | ||||
| 	UserReadBasicAllScope ScopeType = "User.ReadBasic.All" | ||||
|  | ||||
| 	// UserReadAllScope allows the app to read the full set of profile properties, reports, and managers of other users | ||||
| 	// in your organization, on behalf of the signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	UserReadAllScope ScopeType = "User.Read.All" | ||||
|  | ||||
| 	// UserReadWriteAllScope allows the app to read and write the full set of profile properties, reports, and managers | ||||
| 	// of other users in your organization, on behalf of the signed-in user. Also allows the app to create and delete | ||||
| 	// users as well as reset user passwords on behalf of the signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	UserReadWriteAllScope ScopeType = "User.ReadWrite.All" | ||||
|  | ||||
| 	// UserInviteAllScope allows the app to invite guest users to your organization, on behalf of the signed-in user. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	UserInviteAllScope ScopeType = "User.Invite.All" | ||||
|  | ||||
| 	// UserExportAllScope allows the app to export an organizational user's data, when performed by a Company | ||||
| 	// Administrator. | ||||
| 	// | ||||
| 	// requires admin consent | ||||
| 	UserExportAllScope ScopeType = "User.Export.All" | ||||
| ) | ||||
|  | ||||
| // User Activity Permissions | ||||
| // | ||||
| // UserActivity.ReadWrite.CreatedByApp is valid for both Microsoft accounts and work or school accounts. | ||||
| // | ||||
| // The CreatedByApp constraint associated with this permission indicates the service will apply implicit filtering to | ||||
| // results based on the identity of the calling app, either the MSA app id or a set of app ids configured for a | ||||
| // cross-platform application identity. | ||||
| const ( | ||||
| 	// UserActivityReadWriteCreatedByAppScope allows the app to read and report the signed-in user's activity in the | ||||
| 	// app. | ||||
| 	UserActivityReadWriteCreatedByAppScope ScopeType = "UserActivity.ReadWrite.CreatedByApp" | ||||
| ) | ||||
							
								
								
									
										63
									
								
								vendor/github.com/markbates/goth/providers/azureadv2/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								vendor/github.com/markbates/goth/providers/azureadv2/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| package azureadv2 | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| ) | ||||
|  | ||||
| // Session is the implementation of `goth.Session` | ||||
| type Session struct { | ||||
| 	AuthURL      string    `json:"au"` | ||||
| 	AccessToken  string    `json:"at"` | ||||
| 	RefreshToken string    `json:"rt"` | ||||
| 	ExpiresAt    time.Time `json:"exp"` | ||||
| } | ||||
|  | ||||
| // GetAuthURL will return the URL set by calling the `BeginAuth` func | ||||
| func (s Session) GetAuthURL() (string, error) { | ||||
| 	if s.AuthURL == "" { | ||||
| 		return "", errors.New(goth.NoAuthUrlErrorMessage) | ||||
| 	} | ||||
|  | ||||
| 	return s.AuthURL, nil | ||||
| } | ||||
|  | ||||
| // Authorize the session with AzureAD and return the access token to be stored for future use. | ||||
| func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) { | ||||
| 	p := provider.(*Provider) | ||||
| 	token, err := p.config.Exchange(goth.ContextForClient(p.Client()), params.Get("code")) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if !token.Valid() { | ||||
| 		return "", errors.New("invalid token received from provider") | ||||
| 	} | ||||
|  | ||||
| 	s.AccessToken = token.AccessToken | ||||
| 	s.RefreshToken = token.RefreshToken | ||||
| 	s.ExpiresAt = token.Expiry | ||||
|  | ||||
| 	return token.AccessToken, err | ||||
| } | ||||
|  | ||||
| // Marshal the session into a string | ||||
| func (s Session) Marshal() string { | ||||
| 	b, _ := json.Marshal(s) | ||||
| 	return string(b) | ||||
| } | ||||
|  | ||||
| func (s Session) String() string { | ||||
| 	return s.Marshal() | ||||
| } | ||||
|  | ||||
| // UnmarshalSession wil unmarshal a JSON string into a session. | ||||
| func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { | ||||
| 	session := &Session{} | ||||
| 	err := json.NewDecoder(strings.NewReader(data)).Decode(session) | ||||
| 	return session, err | ||||
| } | ||||
							
								
								
									
										190
									
								
								vendor/github.com/markbates/goth/providers/microsoftonline/microsoftonline.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								vendor/github.com/markbates/goth/providers/microsoftonline/microsoftonline.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,190 @@ | ||||
| // Package microsoftonline implements the OAuth2 protocol for authenticating users through microsoftonline. | ||||
| // This package can be used as a reference implementation of an OAuth2 provider for Goth. | ||||
| // To use this package, your application need to be registered in [Application Registration Portal](https://apps.dev.microsoft.com/) | ||||
| package microsoftonline | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/markbates/going/defaults" | ||||
| 	"github.com/markbates/goth" | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	authURL         string = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize" | ||||
| 	tokenURL        string = "https://login.microsoftonline.com/common/oauth2/v2.0/token" | ||||
| 	endpointProfile string = "https://graph.microsoft.com/v1.0/me" | ||||
| ) | ||||
|  | ||||
| var defaultScopes = []string{"openid", "offline_access", "user.read"} | ||||
|  | ||||
| // New creates a new microsoftonline provider, and sets up important connection details. | ||||
| // You should always call `microsoftonline.New` to get a new Provider. Never try to create | ||||
| // one manually. | ||||
| func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { | ||||
| 	p := &Provider{ | ||||
| 		ClientKey:    clientKey, | ||||
| 		Secret:       secret, | ||||
| 		CallbackURL:  callbackURL, | ||||
| 		providerName: "microsoftonline", | ||||
| 	} | ||||
|  | ||||
| 	p.config = newConfig(p, scopes) | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| // Provider is the implementation of `goth.Provider` for accessing microsoftonline. | ||||
| type Provider struct { | ||||
| 	ClientKey    string | ||||
| 	Secret       string | ||||
| 	CallbackURL  string | ||||
| 	HTTPClient   *http.Client | ||||
| 	config       *oauth2.Config | ||||
| 	providerName string | ||||
| 	tenant       string | ||||
| } | ||||
|  | ||||
| // Name is the name used to retrieve this provider later. | ||||
| func (p *Provider) Name() string { | ||||
| 	return p.providerName | ||||
| } | ||||
|  | ||||
| // SetName is to update the name of the provider (needed in case of multiple providers of 1 type) | ||||
| func (p *Provider) SetName(name string) { | ||||
| 	p.providerName = name | ||||
| } | ||||
|  | ||||
| // Client is HTTP client to be used in all fetch operations. | ||||
| func (p *Provider) Client() *http.Client { | ||||
| 	return goth.HTTPClientWithFallBack(p.HTTPClient) | ||||
| } | ||||
|  | ||||
| // Debug is a no-op for the facebook package. | ||||
| func (p *Provider) Debug(debug bool) {} | ||||
|  | ||||
| // BeginAuth asks MicrosoftOnline for an authentication end-point. | ||||
| func (p *Provider) BeginAuth(state string) (goth.Session, error) { | ||||
| 	authURL := p.config.AuthCodeURL(state) | ||||
| 	return &Session{ | ||||
| 		AuthURL: authURL, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // FetchUser will go to MicrosoftOnline and access basic information about the user. | ||||
| func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { | ||||
| 	msSession := session.(*Session) | ||||
| 	user := goth.User{ | ||||
| 		AccessToken: msSession.AccessToken, | ||||
| 		Provider:    p.Name(), | ||||
| 		ExpiresAt:   msSession.ExpiresAt, | ||||
| 	} | ||||
|  | ||||
| 	if user.AccessToken == "" { | ||||
| 		return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName) | ||||
| 	} | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", endpointProfile, nil) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
| 	req.Header.Set(authorizationHeader(msSession)) | ||||
|  | ||||
| 	response, err := p.Client().Do(req) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
| 	defer response.Body.Close() | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode) | ||||
| 	} | ||||
|  | ||||
| 	user.AccessToken = msSession.AccessToken | ||||
|  | ||||
| 	err = userFromReader(response.Body, &user) | ||||
| 	return user, err | ||||
| } | ||||
|  | ||||
| // RefreshTokenAvailable refresh token is provided by auth provider or not | ||||
| // not available for microsoft online as session size hit the limit of max cookie size | ||||
| func (p *Provider) RefreshTokenAvailable() bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| //RefreshToken get new access token based on the refresh token | ||||
| func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { | ||||
| 	if refreshToken == "" { | ||||
| 		return nil, fmt.Errorf("No refresh token provided") | ||||
| 	} | ||||
|  | ||||
| 	token := &oauth2.Token{RefreshToken: refreshToken} | ||||
| 	ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token) | ||||
| 	newToken, err := ts.Token() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return newToken, err | ||||
| } | ||||
|  | ||||
| func newConfig(provider *Provider, scopes []string) *oauth2.Config { | ||||
| 	c := &oauth2.Config{ | ||||
| 		ClientID:     provider.ClientKey, | ||||
| 		ClientSecret: provider.Secret, | ||||
| 		RedirectURL:  provider.CallbackURL, | ||||
| 		Endpoint: oauth2.Endpoint{ | ||||
| 			AuthURL:  authURL, | ||||
| 			TokenURL: tokenURL, | ||||
| 		}, | ||||
| 		Scopes: []string{}, | ||||
| 	} | ||||
|  | ||||
| 	c.Scopes = append(c.Scopes, scopes...) | ||||
| 	if len(scopes) == 0 { | ||||
| 		c.Scopes = append(c.Scopes, defaultScopes...) | ||||
| 	} | ||||
|  | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| func userFromReader(r io.Reader, user *goth.User) error { | ||||
| 	buf := &bytes.Buffer{} | ||||
| 	tee := io.TeeReader(r, buf) | ||||
|  | ||||
| 	u := struct { | ||||
| 		ID                string `json:"id"` | ||||
| 		Name              string `json:"displayName"` | ||||
| 		Email             string `json:"mail"` | ||||
| 		FirstName         string `json:"givenName"` | ||||
| 		LastName          string `json:"surname"` | ||||
| 		UserPrincipalName string `json:"userPrincipalName"` | ||||
| 	}{} | ||||
|  | ||||
| 	if err := json.NewDecoder(tee).Decode(&u); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	raw := map[string]interface{}{} | ||||
| 	if err := json.NewDecoder(buf).Decode(&raw); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	user.UserID = u.ID | ||||
| 	user.Email = defaults.String(u.Email, u.UserPrincipalName) | ||||
| 	user.Name = u.Name | ||||
| 	user.NickName = u.Name | ||||
| 	user.FirstName = u.FirstName | ||||
| 	user.LastName = u.LastName | ||||
| 	user.RawData = raw | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func authorizationHeader(session *Session) (string, string) { | ||||
| 	return "Authorization", fmt.Sprintf("Bearer %s", session.AccessToken) | ||||
| } | ||||
							
								
								
									
										62
									
								
								vendor/github.com/markbates/goth/providers/microsoftonline/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								vendor/github.com/markbates/goth/providers/microsoftonline/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| package microsoftonline | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| ) | ||||
|  | ||||
| // Session is the implementation of `goth.Session` for accessing microsoftonline. | ||||
| // Refresh token not available for microsoft online: session size hit the limit of max cookie size | ||||
| type Session struct { | ||||
| 	AuthURL     string | ||||
| 	AccessToken string | ||||
| 	ExpiresAt   time.Time | ||||
| } | ||||
|  | ||||
| // GetAuthURL will return the URL set by calling the `BeginAuth` function on the Facebook provider. | ||||
| func (s Session) GetAuthURL() (string, error) { | ||||
| 	if s.AuthURL == "" { | ||||
| 		return "", errors.New(goth.NoAuthUrlErrorMessage) | ||||
| 	} | ||||
|  | ||||
| 	return s.AuthURL, nil | ||||
| } | ||||
|  | ||||
| // Authorize the session with Facebook and return the access token to be stored for future use. | ||||
| func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) { | ||||
| 	p := provider.(*Provider) | ||||
| 	token, err := p.config.Exchange(goth.ContextForClient(p.Client()), params.Get("code")) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if !token.Valid() { | ||||
| 		return "", errors.New("Invalid token received from provider") | ||||
| 	} | ||||
|  | ||||
| 	s.AccessToken = token.AccessToken | ||||
| 	s.ExpiresAt = token.Expiry | ||||
|  | ||||
| 	return token.AccessToken, err | ||||
| } | ||||
|  | ||||
| // Marshal the session into a string | ||||
| func (s Session) Marshal() string { | ||||
| 	b, _ := json.Marshal(s) | ||||
| 	return string(b) | ||||
| } | ||||
|  | ||||
| func (s Session) String() string { | ||||
| 	return s.Marshal() | ||||
| } | ||||
|  | ||||
| // UnmarshalSession wil unmarshal a JSON string into a session. | ||||
| func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { | ||||
| 	session := &Session{} | ||||
| 	err := json.NewDecoder(strings.NewReader(data)).Decode(session) | ||||
| 	return session, err | ||||
| } | ||||
							
								
								
									
										5
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							| @@ -570,10 +570,14 @@ github.com/mailru/easyjson | ||||
| github.com/mailru/easyjson/buffer | ||||
| github.com/mailru/easyjson/jlexer | ||||
| github.com/mailru/easyjson/jwriter | ||||
| # github.com/markbates/going v1.0.0 | ||||
| github.com/markbates/going/defaults | ||||
| # github.com/markbates/goth v1.68.0 | ||||
| ## explicit | ||||
| github.com/markbates/goth | ||||
| github.com/markbates/goth/gothic | ||||
| github.com/markbates/goth/providers/azuread | ||||
| github.com/markbates/goth/providers/azureadv2 | ||||
| github.com/markbates/goth/providers/bitbucket | ||||
| github.com/markbates/goth/providers/discord | ||||
| github.com/markbates/goth/providers/dropbox | ||||
| @@ -583,6 +587,7 @@ github.com/markbates/goth/providers/github | ||||
| github.com/markbates/goth/providers/gitlab | ||||
| github.com/markbates/goth/providers/google | ||||
| github.com/markbates/goth/providers/mastodon | ||||
| github.com/markbates/goth/providers/microsoftonline | ||||
| github.com/markbates/goth/providers/nextcloud | ||||
| github.com/markbates/goth/providers/openidConnect | ||||
| github.com/markbates/goth/providers/twitter | ||||
|   | ||||
| @@ -2027,19 +2027,17 @@ function initAdmin() { | ||||
|  | ||||
|     const provider = $('#oauth2_provider').val(); | ||||
|     switch (provider) { | ||||
|       case 'gitea': | ||||
|       case 'nextcloud': | ||||
|       case 'mastodon': | ||||
|         $('#oauth2_use_custom_url').attr('checked', 'checked'); | ||||
|         // fallthrough intentional | ||||
|       case 'github': | ||||
|       case 'gitlab': | ||||
|         $('.oauth2_use_custom_url').show(); | ||||
|         break; | ||||
|       case 'openidConnect': | ||||
|         $('.open_id_connect_auto_discovery_url input').attr('required', 'required'); | ||||
|         $('.open_id_connect_auto_discovery_url').show(); | ||||
|         break; | ||||
|       default: | ||||
|         if ($(`#${provider}_customURLSettings`).data('required')) { | ||||
|           $('#oauth2_use_custom_url').attr('checked', 'checked'); | ||||
|         } | ||||
|         if ($(`#${provider}_customURLSettings`).data('available')) { | ||||
|           $('.oauth2_use_custom_url').show(); | ||||
|         } | ||||
|     } | ||||
|     onOAuth2UseCustomURLChange(applyDefaultValues); | ||||
|   } | ||||
| @@ -2050,29 +2048,14 @@ function initAdmin() { | ||||
|     $('.oauth2_use_custom_url_field input[required]').removeAttr('required'); | ||||
|  | ||||
|     if ($('#oauth2_use_custom_url').is(':checked')) { | ||||
|       if (applyDefaultValues) { | ||||
|         $('#oauth2_token_url').val($(`#${provider}_token_url`).val()); | ||||
|         $('#oauth2_auth_url').val($(`#${provider}_auth_url`).val()); | ||||
|         $('#oauth2_profile_url').val($(`#${provider}_profile_url`).val()); | ||||
|         $('#oauth2_email_url').val($(`#${provider}_email_url`).val()); | ||||
|       } | ||||
|  | ||||
|       switch (provider) { | ||||
|         case 'github': | ||||
|           $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input, .oauth2_email_url input').attr('required', 'required'); | ||||
|           $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url, .oauth2_email_url').show(); | ||||
|           break; | ||||
|         case 'nextcloud': | ||||
|         case 'gitea': | ||||
|         case 'gitlab': | ||||
|           $('.oauth2_token_url input, .oauth2_auth_url input, .oauth2_profile_url input').attr('required', 'required'); | ||||
|           $('.oauth2_token_url, .oauth2_auth_url, .oauth2_profile_url').show(); | ||||
|           $('#oauth2_email_url').val(''); | ||||
|           break; | ||||
|         case 'mastodon': | ||||
|           $('.oauth2_auth_url input').attr('required', 'required'); | ||||
|           $('.oauth2_auth_url').show(); | ||||
|           break; | ||||
|       for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) { | ||||
|         if (applyDefaultValues) { | ||||
|           $(`#oauth2_${custom}`).val($(`#${provider}_${custom}`).val()); | ||||
|         } | ||||
|         if ($(`#${provider}_${custom}`).data('available')) { | ||||
|           $(`.oauth2_${custom} input`).attr('required', 'required'); | ||||
|           $(`.oauth2_${custom}`).show(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user