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 }