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 }