code.gitea.io/gitea@v1.21.7/services/auth/source/oauth2/providers.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package oauth2
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"html"
    10  	"html/template"
    11  	"net/url"
    12  	"sort"
    13  
    14  	"code.gitea.io/gitea/models/auth"
    15  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/setting"
    17  
    18  	"github.com/markbates/goth"
    19  )
    20  
    21  // Provider is an interface for describing a single OAuth2 provider
    22  type Provider interface {
    23  	Name() string
    24  	DisplayName() string
    25  	IconHTML(size int) template.HTML
    26  	CustomURLSettings() *CustomURLSettings
    27  }
    28  
    29  // GothProviderCreator provides a function to create a goth.Provider
    30  type GothProviderCreator interface {
    31  	CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error)
    32  }
    33  
    34  // GothProvider is an interface for describing a single OAuth2 provider
    35  type GothProvider interface {
    36  	Provider
    37  	GothProviderCreator
    38  }
    39  
    40  // AuthSourceProvider provides a provider for an AuthSource. Multiple auth sources could use the same registered GothProvider
    41  // So each auth source should have its own DisplayName and IconHTML for display.
    42  // The Name is the GothProvider's name, to help to find the GothProvider to sign in.
    43  // The DisplayName is the auth source config's name, site admin set it on the admin page, the IconURL can also be set there.
    44  type AuthSourceProvider struct {
    45  	GothProvider
    46  	sourceName, iconURL string
    47  }
    48  
    49  func (p *AuthSourceProvider) Name() string {
    50  	return p.GothProvider.Name()
    51  }
    52  
    53  func (p *AuthSourceProvider) DisplayName() string {
    54  	return p.sourceName
    55  }
    56  
    57  func (p *AuthSourceProvider) IconHTML(size int) template.HTML {
    58  	if p.iconURL != "" {
    59  		img := fmt.Sprintf(`<img class="gt-object-contain gt-mr-3" width="%d" height="%d" src="%s" alt="%s">`,
    60  			size,
    61  			size,
    62  			html.EscapeString(p.iconURL), html.EscapeString(p.DisplayName()),
    63  		)
    64  		return template.HTML(img)
    65  	}
    66  	return p.GothProvider.IconHTML(size)
    67  }
    68  
    69  // Providers contains the map of registered OAuth2 providers in Gitea (based on goth)
    70  // key is used to map the OAuth2Provider with the goth provider type (also in AuthSource.OAuth2Config.Provider)
    71  // value is used to store display data
    72  var gothProviders = map[string]GothProvider{}
    73  
    74  // RegisterGothProvider registers a GothProvider
    75  func RegisterGothProvider(provider GothProvider) {
    76  	if _, has := gothProviders[provider.Name()]; has {
    77  		log.Fatal("Duplicate oauth2provider type provided: %s", provider.Name())
    78  	}
    79  	gothProviders[provider.Name()] = provider
    80  }
    81  
    82  // GetOAuth2Providers returns the map of unconfigured OAuth2 providers
    83  // key is used as technical name (like in the callbackURL)
    84  // values to display
    85  func GetOAuth2Providers() []Provider {
    86  	providers := make([]Provider, 0, len(gothProviders))
    87  
    88  	for _, provider := range gothProviders {
    89  		providers = append(providers, provider)
    90  	}
    91  	sort.Slice(providers, func(i, j int) bool {
    92  		return providers[i].Name() < providers[j].Name()
    93  	})
    94  	return providers
    95  }
    96  
    97  // GetOAuth2ProvidersMap returns the map of configured active OAuth2 providers
    98  // key is used as technical name (like in the callbackURL)
    99  // values to display
   100  func GetOAuth2ProvidersMap(onlyActive bool) ([]string, map[string]Provider, error) {
   101  	// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type
   102  	authSources, err := auth.GetOAuth2ProviderSources(onlyActive)
   103  	if err != nil {
   104  		return nil, nil, err
   105  	}
   106  
   107  	var orderedKeys []string
   108  	providers := make(map[string]Provider)
   109  	for _, source := range authSources {
   110  		oauth2Cfg, ok := source.Cfg.(*Source)
   111  		if !ok {
   112  			log.Error("Invalid OAuth2 source config: %v", oauth2Cfg)
   113  			continue
   114  		}
   115  		gothProv := gothProviders[oauth2Cfg.Provider]
   116  		providers[source.Name] = &AuthSourceProvider{GothProvider: gothProv, sourceName: source.Name, iconURL: oauth2Cfg.IconURL}
   117  		orderedKeys = append(orderedKeys, source.Name)
   118  	}
   119  
   120  	sort.Strings(orderedKeys)
   121  
   122  	return orderedKeys, providers, nil
   123  }
   124  
   125  // RegisterProviderWithGothic register a OAuth2 provider in goth lib
   126  func RegisterProviderWithGothic(providerName string, source *Source) error {
   127  	provider, err := createProvider(providerName, source)
   128  
   129  	if err == nil && provider != nil {
   130  		gothRWMutex.Lock()
   131  		defer gothRWMutex.Unlock()
   132  
   133  		goth.UseProviders(provider)
   134  	}
   135  
   136  	return err
   137  }
   138  
   139  // RemoveProviderFromGothic removes the given OAuth2 provider from the goth lib
   140  func RemoveProviderFromGothic(providerName string) {
   141  	gothRWMutex.Lock()
   142  	defer gothRWMutex.Unlock()
   143  
   144  	delete(goth.GetProviders(), providerName)
   145  }
   146  
   147  // ClearProviders clears all OAuth2 providers from the goth lib
   148  func ClearProviders() {
   149  	gothRWMutex.Lock()
   150  	defer gothRWMutex.Unlock()
   151  
   152  	goth.ClearProviders()
   153  }
   154  
   155  var ErrAuthSourceNotActivated = errors.New("auth source is not activated")
   156  
   157  // used to create different types of goth providers
   158  func createProvider(providerName string, source *Source) (goth.Provider, error) {
   159  	callbackURL := setting.AppURL + "user/oauth2/" + url.PathEscape(providerName) + "/callback"
   160  
   161  	var provider goth.Provider
   162  	var err error
   163  
   164  	p, ok := gothProviders[source.Provider]
   165  	if !ok {
   166  		return nil, ErrAuthSourceNotActivated
   167  	}
   168  
   169  	provider, err = p.CreateGothProvider(providerName, callbackURL, source)
   170  	if err != nil {
   171  		return provider, err
   172  	}
   173  
   174  	// always set the name if provider is created so we can support multiple setups of 1 provider
   175  	if err == nil && provider != nil {
   176  		provider.SetName(providerName)
   177  	}
   178  
   179  	return provider, err
   180  }