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