code.gitea.io/gitea@v1.21.7/routers/web/admin/auths.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package admin 5 6 import ( 7 "errors" 8 "fmt" 9 "net/http" 10 "net/url" 11 "regexp" 12 "strconv" 13 "strings" 14 15 "code.gitea.io/gitea/models/auth" 16 "code.gitea.io/gitea/modules/auth/pam" 17 "code.gitea.io/gitea/modules/base" 18 "code.gitea.io/gitea/modules/context" 19 "code.gitea.io/gitea/modules/log" 20 "code.gitea.io/gitea/modules/setting" 21 "code.gitea.io/gitea/modules/util" 22 "code.gitea.io/gitea/modules/web" 23 auth_service "code.gitea.io/gitea/services/auth" 24 "code.gitea.io/gitea/services/auth/source/ldap" 25 "code.gitea.io/gitea/services/auth/source/oauth2" 26 pam_service "code.gitea.io/gitea/services/auth/source/pam" 27 "code.gitea.io/gitea/services/auth/source/smtp" 28 "code.gitea.io/gitea/services/auth/source/sspi" 29 "code.gitea.io/gitea/services/forms" 30 31 "xorm.io/xorm/convert" 32 ) 33 34 const ( 35 tplAuths base.TplName = "admin/auth/list" 36 tplAuthNew base.TplName = "admin/auth/new" 37 tplAuthEdit base.TplName = "admin/auth/edit" 38 ) 39 40 var ( 41 separatorAntiPattern = regexp.MustCompile(`[^\w-\.]`) 42 langCodePattern = regexp.MustCompile(`^[a-z]{2}-[A-Z]{2}$`) 43 ) 44 45 // Authentications show authentication config page 46 func Authentications(ctx *context.Context) { 47 ctx.Data["Title"] = ctx.Tr("admin.authentication") 48 ctx.Data["PageIsAdminAuthentications"] = true 49 50 var err error 51 ctx.Data["Sources"], err = auth.Sources() 52 if err != nil { 53 ctx.ServerError("auth.Sources", err) 54 return 55 } 56 57 ctx.Data["Total"] = auth.CountSources() 58 ctx.HTML(http.StatusOK, tplAuths) 59 } 60 61 type dropdownItem struct { 62 Name string 63 Type any 64 } 65 66 var ( 67 authSources = func() []dropdownItem { 68 items := []dropdownItem{ 69 {auth.LDAP.String(), auth.LDAP}, 70 {auth.DLDAP.String(), auth.DLDAP}, 71 {auth.SMTP.String(), auth.SMTP}, 72 {auth.OAuth2.String(), auth.OAuth2}, 73 {auth.SSPI.String(), auth.SSPI}, 74 } 75 if pam.Supported { 76 items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM}) 77 } 78 return items 79 }() 80 81 securityProtocols = []dropdownItem{ 82 {ldap.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted], ldap.SecurityProtocolUnencrypted}, 83 {ldap.SecurityProtocolNames[ldap.SecurityProtocolLDAPS], ldap.SecurityProtocolLDAPS}, 84 {ldap.SecurityProtocolNames[ldap.SecurityProtocolStartTLS], ldap.SecurityProtocolStartTLS}, 85 } 86 ) 87 88 // NewAuthSource render adding a new auth source page 89 func NewAuthSource(ctx *context.Context) { 90 ctx.Data["Title"] = ctx.Tr("admin.auths.new") 91 ctx.Data["PageIsAdminAuthentications"] = true 92 93 ctx.Data["type"] = auth.LDAP.Int() 94 ctx.Data["CurrentTypeName"] = auth.Names[auth.LDAP] 95 ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted] 96 ctx.Data["smtp_auth"] = "PLAIN" 97 ctx.Data["is_active"] = true 98 ctx.Data["is_sync_enabled"] = true 99 ctx.Data["AuthSources"] = authSources 100 ctx.Data["SecurityProtocols"] = securityProtocols 101 ctx.Data["SMTPAuths"] = smtp.Authenticators 102 oauth2providers := oauth2.GetOAuth2Providers() 103 ctx.Data["OAuth2Providers"] = oauth2providers 104 105 ctx.Data["SSPIAutoCreateUsers"] = true 106 ctx.Data["SSPIAutoActivateUsers"] = true 107 ctx.Data["SSPIStripDomainNames"] = true 108 ctx.Data["SSPISeparatorReplacement"] = "_" 109 ctx.Data["SSPIDefaultLanguage"] = "" 110 111 // only the first as default 112 ctx.Data["oauth2_provider"] = oauth2providers[0].Name() 113 114 ctx.HTML(http.StatusOK, tplAuthNew) 115 } 116 117 func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source { 118 var pageSize uint32 119 if form.UsePagedSearch { 120 pageSize = uint32(form.SearchPageSize) 121 } 122 return &ldap.Source{ 123 Name: form.Name, 124 Host: form.Host, 125 Port: form.Port, 126 SecurityProtocol: ldap.SecurityProtocol(form.SecurityProtocol), 127 SkipVerify: form.SkipVerify, 128 BindDN: form.BindDN, 129 UserDN: form.UserDN, 130 BindPassword: form.BindPassword, 131 UserBase: form.UserBase, 132 AttributeUsername: form.AttributeUsername, 133 AttributeName: form.AttributeName, 134 AttributeSurname: form.AttributeSurname, 135 AttributeMail: form.AttributeMail, 136 AttributesInBind: form.AttributesInBind, 137 AttributeSSHPublicKey: form.AttributeSSHPublicKey, 138 AttributeAvatar: form.AttributeAvatar, 139 SearchPageSize: pageSize, 140 Filter: form.Filter, 141 GroupsEnabled: form.GroupsEnabled, 142 GroupDN: form.GroupDN, 143 GroupFilter: form.GroupFilter, 144 GroupMemberUID: form.GroupMemberUID, 145 GroupTeamMap: form.GroupTeamMap, 146 GroupTeamMapRemoval: form.GroupTeamMapRemoval, 147 UserUID: form.UserUID, 148 AdminFilter: form.AdminFilter, 149 RestrictedFilter: form.RestrictedFilter, 150 AllowDeactivateAll: form.AllowDeactivateAll, 151 Enabled: true, 152 SkipLocalTwoFA: form.SkipLocalTwoFA, 153 } 154 } 155 156 func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source { 157 return &smtp.Source{ 158 Auth: form.SMTPAuth, 159 Host: form.SMTPHost, 160 Port: form.SMTPPort, 161 AllowedDomains: form.AllowedDomains, 162 ForceSMTPS: form.ForceSMTPS, 163 SkipVerify: form.SkipVerify, 164 HeloHostname: form.HeloHostname, 165 DisableHelo: form.DisableHelo, 166 SkipLocalTwoFA: form.SkipLocalTwoFA, 167 } 168 } 169 170 func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { 171 var customURLMapping *oauth2.CustomURLMapping 172 if form.Oauth2UseCustomURL { 173 customURLMapping = &oauth2.CustomURLMapping{ 174 TokenURL: form.Oauth2TokenURL, 175 AuthURL: form.Oauth2AuthURL, 176 ProfileURL: form.Oauth2ProfileURL, 177 EmailURL: form.Oauth2EmailURL, 178 Tenant: form.Oauth2Tenant, 179 } 180 } else { 181 customURLMapping = nil 182 } 183 var scopes []string 184 for _, s := range strings.Split(form.Oauth2Scopes, ",") { 185 s = strings.TrimSpace(s) 186 if s != "" { 187 scopes = append(scopes, s) 188 } 189 } 190 191 return &oauth2.Source{ 192 Provider: form.Oauth2Provider, 193 ClientID: form.Oauth2Key, 194 ClientSecret: form.Oauth2Secret, 195 OpenIDConnectAutoDiscoveryURL: form.OpenIDConnectAutoDiscoveryURL, 196 CustomURLMapping: customURLMapping, 197 IconURL: form.Oauth2IconURL, 198 Scopes: scopes, 199 RequiredClaimName: form.Oauth2RequiredClaimName, 200 RequiredClaimValue: form.Oauth2RequiredClaimValue, 201 SkipLocalTwoFA: form.SkipLocalTwoFA, 202 GroupClaimName: form.Oauth2GroupClaimName, 203 RestrictedGroup: form.Oauth2RestrictedGroup, 204 AdminGroup: form.Oauth2AdminGroup, 205 GroupTeamMap: form.Oauth2GroupTeamMap, 206 GroupTeamMapRemoval: form.Oauth2GroupTeamMapRemoval, 207 } 208 } 209 210 func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) { 211 if util.IsEmptyString(form.SSPISeparatorReplacement) { 212 ctx.Data["Err_SSPISeparatorReplacement"] = true 213 return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.require_error")) 214 } 215 if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) { 216 ctx.Data["Err_SSPISeparatorReplacement"] = true 217 return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.alpha_dash_dot_error")) 218 } 219 220 if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) { 221 ctx.Data["Err_SSPIDefaultLanguage"] = true 222 return nil, errors.New(ctx.Tr("form.lang_select_error")) 223 } 224 225 return &sspi.Source{ 226 AutoCreateUsers: form.SSPIAutoCreateUsers, 227 AutoActivateUsers: form.SSPIAutoActivateUsers, 228 StripDomainNames: form.SSPIStripDomainNames, 229 SeparatorReplacement: form.SSPISeparatorReplacement, 230 DefaultLanguage: form.SSPIDefaultLanguage, 231 }, nil 232 } 233 234 // NewAuthSourcePost response for adding an auth source 235 func NewAuthSourcePost(ctx *context.Context) { 236 form := *web.GetForm(ctx).(*forms.AuthenticationForm) 237 ctx.Data["Title"] = ctx.Tr("admin.auths.new") 238 ctx.Data["PageIsAdminAuthentications"] = true 239 240 ctx.Data["CurrentTypeName"] = auth.Type(form.Type).String() 241 ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocol(form.SecurityProtocol)] 242 ctx.Data["AuthSources"] = authSources 243 ctx.Data["SecurityProtocols"] = securityProtocols 244 ctx.Data["SMTPAuths"] = smtp.Authenticators 245 oauth2providers := oauth2.GetOAuth2Providers() 246 ctx.Data["OAuth2Providers"] = oauth2providers 247 248 ctx.Data["SSPIAutoCreateUsers"] = true 249 ctx.Data["SSPIAutoActivateUsers"] = true 250 ctx.Data["SSPIStripDomainNames"] = true 251 ctx.Data["SSPISeparatorReplacement"] = "_" 252 ctx.Data["SSPIDefaultLanguage"] = "" 253 254 hasTLS := false 255 var config convert.Conversion 256 switch auth.Type(form.Type) { 257 case auth.LDAP, auth.DLDAP: 258 config = parseLDAPConfig(form) 259 hasTLS = ldap.SecurityProtocol(form.SecurityProtocol) > ldap.SecurityProtocolUnencrypted 260 case auth.SMTP: 261 config = parseSMTPConfig(form) 262 hasTLS = true 263 case auth.PAM: 264 config = &pam_service.Source{ 265 ServiceName: form.PAMServiceName, 266 EmailDomain: form.PAMEmailDomain, 267 SkipLocalTwoFA: form.SkipLocalTwoFA, 268 } 269 case auth.OAuth2: 270 config = parseOAuth2Config(form) 271 oauth2Config := config.(*oauth2.Source) 272 if oauth2Config.Provider == "openidConnect" { 273 discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL) 274 if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { 275 ctx.Data["Err_DiscoveryURL"] = true 276 ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthNew, form) 277 return 278 } 279 } 280 case auth.SSPI: 281 var err error 282 config, err = parseSSPIConfig(ctx, form) 283 if err != nil { 284 ctx.RenderWithErr(err.Error(), tplAuthNew, form) 285 return 286 } 287 existing, err := auth.SourcesByType(auth.SSPI) 288 if err != nil || len(existing) > 0 { 289 ctx.Data["Err_Type"] = true 290 ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form) 291 return 292 } 293 default: 294 ctx.Error(http.StatusBadRequest) 295 return 296 } 297 ctx.Data["HasTLS"] = hasTLS 298 299 if ctx.HasError() { 300 ctx.HTML(http.StatusOK, tplAuthNew) 301 return 302 } 303 304 if err := auth.CreateSource(&auth.Source{ 305 Type: auth.Type(form.Type), 306 Name: form.Name, 307 IsActive: form.IsActive, 308 IsSyncEnabled: form.IsSyncEnabled, 309 Cfg: config, 310 }); err != nil { 311 if auth.IsErrSourceAlreadyExist(err) { 312 ctx.Data["Err_Name"] = true 313 ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form) 314 } else if oauth2.IsErrOpenIDConnectInitialize(err) { 315 ctx.Data["Err_DiscoveryURL"] = true 316 unwrapped := err.(oauth2.ErrOpenIDConnectInitialize).Unwrap() 317 ctx.RenderWithErr(ctx.Tr("admin.auths.unable_to_initialize_openid", unwrapped), tplAuthNew, form) 318 } else { 319 ctx.ServerError("auth.CreateSource", err) 320 } 321 return 322 } 323 324 log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name) 325 326 ctx.Flash.Success(ctx.Tr("admin.auths.new_success", form.Name)) 327 ctx.Redirect(setting.AppSubURL + "/admin/auths") 328 } 329 330 // EditAuthSource render editing auth source page 331 func EditAuthSource(ctx *context.Context) { 332 ctx.Data["Title"] = ctx.Tr("admin.auths.edit") 333 ctx.Data["PageIsAdminAuthentications"] = true 334 335 ctx.Data["SecurityProtocols"] = securityProtocols 336 ctx.Data["SMTPAuths"] = smtp.Authenticators 337 oauth2providers := oauth2.GetOAuth2Providers() 338 ctx.Data["OAuth2Providers"] = oauth2providers 339 340 source, err := auth.GetSourceByID(ctx.ParamsInt64(":authid")) 341 if err != nil { 342 ctx.ServerError("auth.GetSourceByID", err) 343 return 344 } 345 ctx.Data["Source"] = source 346 ctx.Data["HasTLS"] = source.HasTLS() 347 348 if source.IsOAuth2() { 349 type Named interface { 350 Name() string 351 } 352 353 for _, provider := range oauth2providers { 354 if provider.Name() == source.Cfg.(Named).Name() { 355 ctx.Data["CurrentOAuth2Provider"] = provider 356 break 357 } 358 } 359 } 360 361 ctx.HTML(http.StatusOK, tplAuthEdit) 362 } 363 364 // EditAuthSourcePost response for editing auth source 365 func EditAuthSourcePost(ctx *context.Context) { 366 form := *web.GetForm(ctx).(*forms.AuthenticationForm) 367 ctx.Data["Title"] = ctx.Tr("admin.auths.edit") 368 ctx.Data["PageIsAdminAuthentications"] = true 369 370 ctx.Data["SMTPAuths"] = smtp.Authenticators 371 oauth2providers := oauth2.GetOAuth2Providers() 372 ctx.Data["OAuth2Providers"] = oauth2providers 373 374 source, err := auth.GetSourceByID(ctx.ParamsInt64(":authid")) 375 if err != nil { 376 ctx.ServerError("auth.GetSourceByID", err) 377 return 378 } 379 ctx.Data["Source"] = source 380 ctx.Data["HasTLS"] = source.HasTLS() 381 382 if ctx.HasError() { 383 ctx.HTML(http.StatusOK, tplAuthEdit) 384 return 385 } 386 387 var config convert.Conversion 388 switch auth.Type(form.Type) { 389 case auth.LDAP, auth.DLDAP: 390 config = parseLDAPConfig(form) 391 case auth.SMTP: 392 config = parseSMTPConfig(form) 393 case auth.PAM: 394 config = &pam_service.Source{ 395 ServiceName: form.PAMServiceName, 396 EmailDomain: form.PAMEmailDomain, 397 } 398 case auth.OAuth2: 399 config = parseOAuth2Config(form) 400 oauth2Config := config.(*oauth2.Source) 401 if oauth2Config.Provider == "openidConnect" { 402 discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL) 403 if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { 404 ctx.Data["Err_DiscoveryURL"] = true 405 ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthEdit, form) 406 return 407 } 408 } 409 case auth.SSPI: 410 config, err = parseSSPIConfig(ctx, form) 411 if err != nil { 412 ctx.RenderWithErr(err.Error(), tplAuthEdit, form) 413 return 414 } 415 default: 416 ctx.Error(http.StatusBadRequest) 417 return 418 } 419 420 source.Name = form.Name 421 source.IsActive = form.IsActive 422 source.IsSyncEnabled = form.IsSyncEnabled 423 source.Cfg = config 424 if err := auth.UpdateSource(source); err != nil { 425 if auth.IsErrSourceAlreadyExist(err) { 426 ctx.Data["Err_Name"] = true 427 ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthEdit, form) 428 } else if oauth2.IsErrOpenIDConnectInitialize(err) { 429 ctx.Flash.Error(err.Error(), true) 430 ctx.Data["Err_DiscoveryURL"] = true 431 ctx.HTML(http.StatusOK, tplAuthEdit) 432 } else { 433 ctx.ServerError("UpdateSource", err) 434 } 435 return 436 } 437 log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID) 438 439 ctx.Flash.Success(ctx.Tr("admin.auths.update_success")) 440 ctx.Redirect(setting.AppSubURL + "/admin/auths/" + strconv.FormatInt(form.ID, 10)) 441 } 442 443 // DeleteAuthSource response for deleting an auth source 444 func DeleteAuthSource(ctx *context.Context) { 445 source, err := auth.GetSourceByID(ctx.ParamsInt64(":authid")) 446 if err != nil { 447 ctx.ServerError("auth.GetSourceByID", err) 448 return 449 } 450 451 if err = auth_service.DeleteSource(source); err != nil { 452 if auth.IsErrSourceInUse(err) { 453 ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used")) 454 } else { 455 ctx.Flash.Error(fmt.Sprintf("auth_service.DeleteSource: %v", err)) 456 } 457 ctx.JSONRedirect(setting.AppSubURL + "/admin/auths/" + url.PathEscape(ctx.Params(":authid"))) 458 return 459 } 460 log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID) 461 462 ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success")) 463 ctx.JSONRedirect(setting.AppSubURL + "/admin/auths") 464 }