code.gitea.io/gitea@v1.21.7/routers/web/auth/openid.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package auth 5 6 import ( 7 "fmt" 8 "net/http" 9 "net/url" 10 11 user_model "code.gitea.io/gitea/models/user" 12 "code.gitea.io/gitea/modules/auth/openid" 13 "code.gitea.io/gitea/modules/base" 14 "code.gitea.io/gitea/modules/context" 15 "code.gitea.io/gitea/modules/log" 16 "code.gitea.io/gitea/modules/setting" 17 "code.gitea.io/gitea/modules/util" 18 "code.gitea.io/gitea/modules/web" 19 "code.gitea.io/gitea/modules/web/middleware" 20 "code.gitea.io/gitea/services/auth" 21 "code.gitea.io/gitea/services/forms" 22 ) 23 24 const ( 25 tplSignInOpenID base.TplName = "user/auth/signin_openid" 26 tplConnectOID base.TplName = "user/auth/signup_openid_connect" 27 tplSignUpOID base.TplName = "user/auth/signup_openid_register" 28 ) 29 30 // SignInOpenID render sign in page 31 func SignInOpenID(ctx *context.Context) { 32 ctx.Data["Title"] = ctx.Tr("sign_in") 33 34 if ctx.FormString("openid.return_to") != "" { 35 signInOpenIDVerify(ctx) 36 return 37 } 38 39 // Check auto-login. 40 isSucceed, err := AutoSignIn(ctx) 41 if err != nil { 42 ctx.ServerError("AutoSignIn", err) 43 return 44 } 45 46 redirectTo := ctx.FormString("redirect_to") 47 if len(redirectTo) > 0 { 48 middleware.SetRedirectToCookie(ctx.Resp, redirectTo) 49 } else { 50 redirectTo = ctx.GetSiteCookie("redirect_to") 51 } 52 53 if isSucceed { 54 middleware.DeleteRedirectToCookie(ctx.Resp) 55 ctx.RedirectToFirst(redirectTo) 56 return 57 } 58 59 ctx.Data["PageIsSignIn"] = true 60 ctx.Data["PageIsLoginOpenID"] = true 61 ctx.HTML(http.StatusOK, tplSignInOpenID) 62 } 63 64 // Check if the given OpenID URI is allowed by blacklist/whitelist 65 func allowedOpenIDURI(uri string) (err error) { 66 // In case a Whitelist is present, URI must be in it 67 // in order to be accepted 68 if len(setting.Service.OpenIDWhitelist) != 0 { 69 for _, pat := range setting.Service.OpenIDWhitelist { 70 if pat.MatchString(uri) { 71 return nil // pass 72 } 73 } 74 // must match one of this or be refused 75 return fmt.Errorf("URI not allowed by whitelist") 76 } 77 78 // A blacklist match expliclty forbids 79 for _, pat := range setting.Service.OpenIDBlacklist { 80 if pat.MatchString(uri) { 81 return fmt.Errorf("URI forbidden by blacklist") 82 } 83 } 84 85 return nil 86 } 87 88 // SignInOpenIDPost response for openid sign in request 89 func SignInOpenIDPost(ctx *context.Context) { 90 form := web.GetForm(ctx).(*forms.SignInOpenIDForm) 91 ctx.Data["Title"] = ctx.Tr("sign_in") 92 ctx.Data["PageIsSignIn"] = true 93 ctx.Data["PageIsLoginOpenID"] = true 94 95 if ctx.HasError() { 96 ctx.HTML(http.StatusOK, tplSignInOpenID) 97 return 98 } 99 100 id, err := openid.Normalize(form.Openid) 101 if err != nil { 102 ctx.RenderWithErr(err.Error(), tplSignInOpenID, &form) 103 return 104 } 105 form.Openid = id 106 107 log.Trace("OpenID uri: " + id) 108 109 err = allowedOpenIDURI(id) 110 if err != nil { 111 ctx.RenderWithErr(err.Error(), tplSignInOpenID, &form) 112 return 113 } 114 115 redirectTo := setting.AppURL + "user/login/openid" 116 url, err := openid.RedirectURL(id, redirectTo, setting.AppURL) 117 if err != nil { 118 log.Error("Error in OpenID redirect URL: %s, %v", redirectTo, err.Error()) 119 ctx.RenderWithErr(fmt.Sprintf("Unable to find OpenID provider in %s", redirectTo), tplSignInOpenID, &form) 120 return 121 } 122 123 // Request optional nickname and email info 124 // NOTE: change to `openid.sreg.required` to require it 125 url += "&openid.ns.sreg=http%3A%2F%2Fopenid.net%2Fextensions%2Fsreg%2F1.1" 126 url += "&openid.sreg.optional=nickname%2Cemail" 127 128 log.Trace("Form-passed openid-remember: %t", form.Remember) 129 130 if err := ctx.Session.Set("openid_signin_remember", form.Remember); err != nil { 131 log.Error("SignInOpenIDPost: Could not set openid_signin_remember in session: %v", err) 132 } 133 if err := ctx.Session.Release(); err != nil { 134 log.Error("SignInOpenIDPost: Unable to save changes to the session: %v", err) 135 } 136 137 ctx.Redirect(url) 138 } 139 140 // signInOpenIDVerify handles response from OpenID provider 141 func signInOpenIDVerify(ctx *context.Context) { 142 log.Trace("Incoming call to: %s", ctx.Req.URL.String()) 143 144 fullURL := setting.AppURL + ctx.Req.URL.String()[1:] 145 log.Trace("Full URL: %s", fullURL) 146 147 id, err := openid.Verify(fullURL) 148 if err != nil { 149 ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ 150 Openid: id, 151 }) 152 return 153 } 154 155 log.Trace("Verified ID: %s", id) 156 157 /* Now we should seek for the user and log him in, or prompt 158 * to register if not found */ 159 160 u, err := user_model.GetUserByOpenID(ctx, id) 161 if err != nil { 162 if !user_model.IsErrUserNotExist(err) { 163 ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ 164 Openid: id, 165 }) 166 return 167 } 168 log.Error("signInOpenIDVerify: %v", err) 169 } 170 if u != nil { 171 log.Trace("User exists, logging in") 172 remember, _ := ctx.Session.Get("openid_signin_remember").(bool) 173 log.Trace("Session stored openid-remember: %t", remember) 174 handleSignIn(ctx, u, remember) 175 return 176 } 177 178 log.Trace("User with openid: %s does not exist, should connect or register", id) 179 180 parsedURL, err := url.Parse(fullURL) 181 if err != nil { 182 ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ 183 Openid: id, 184 }) 185 return 186 } 187 values, err := url.ParseQuery(parsedURL.RawQuery) 188 if err != nil { 189 ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ 190 Openid: id, 191 }) 192 return 193 } 194 email := values.Get("openid.sreg.email") 195 nickname := values.Get("openid.sreg.nickname") 196 197 log.Trace("User has email=%s and nickname=%s", email, nickname) 198 199 if email != "" { 200 u, err = user_model.GetUserByEmail(ctx, email) 201 if err != nil { 202 if !user_model.IsErrUserNotExist(err) { 203 ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ 204 Openid: id, 205 }) 206 return 207 } 208 log.Error("signInOpenIDVerify: %v", err) 209 } 210 if u != nil { 211 log.Trace("Local user %s has OpenID provided email %s", u.LowerName, email) 212 } 213 } 214 215 if u == nil && nickname != "" { 216 u, _ = user_model.GetUserByName(ctx, nickname) 217 if err != nil { 218 if !user_model.IsErrUserNotExist(err) { 219 ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ 220 Openid: id, 221 }) 222 return 223 } 224 } 225 if u != nil { 226 log.Trace("Local user %s has OpenID provided nickname %s", u.LowerName, nickname) 227 } 228 } 229 230 if u != nil { 231 nickname = u.LowerName 232 } 233 if err := updateSession(ctx, nil, map[string]any{ 234 "openid_verified_uri": id, 235 "openid_determined_email": email, 236 "openid_determined_username": nickname, 237 }); err != nil { 238 ctx.ServerError("updateSession", err) 239 return 240 } 241 242 if u != nil || !setting.Service.EnableOpenIDSignUp || setting.Service.AllowOnlyInternalRegistration { 243 ctx.Redirect(setting.AppSubURL + "/user/openid/connect") 244 } else { 245 ctx.Redirect(setting.AppSubURL + "/user/openid/register") 246 } 247 } 248 249 // ConnectOpenID shows a form to connect an OpenID URI to an existing account 250 func ConnectOpenID(ctx *context.Context) { 251 oid, _ := ctx.Session.Get("openid_verified_uri").(string) 252 if oid == "" { 253 ctx.Redirect(setting.AppSubURL + "/user/login/openid") 254 return 255 } 256 ctx.Data["Title"] = "OpenID connect" 257 ctx.Data["PageIsSignIn"] = true 258 ctx.Data["PageIsOpenIDConnect"] = true 259 ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp 260 ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration 261 ctx.Data["OpenID"] = oid 262 userName, _ := ctx.Session.Get("openid_determined_username").(string) 263 if userName != "" { 264 ctx.Data["user_name"] = userName 265 } 266 ctx.HTML(http.StatusOK, tplConnectOID) 267 } 268 269 // ConnectOpenIDPost handles submission of a form to connect an OpenID URI to an existing account 270 func ConnectOpenIDPost(ctx *context.Context) { 271 form := web.GetForm(ctx).(*forms.ConnectOpenIDForm) 272 oid, _ := ctx.Session.Get("openid_verified_uri").(string) 273 if oid == "" { 274 ctx.Redirect(setting.AppSubURL + "/user/login/openid") 275 return 276 } 277 ctx.Data["Title"] = "OpenID connect" 278 ctx.Data["PageIsSignIn"] = true 279 ctx.Data["PageIsOpenIDConnect"] = true 280 ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp 281 ctx.Data["OpenID"] = oid 282 283 u, _, err := auth.UserSignIn(ctx, form.UserName, form.Password) 284 if err != nil { 285 handleSignInError(ctx, form.UserName, &form, tplConnectOID, "ConnectOpenIDPost", err) 286 return 287 } 288 289 // add OpenID for the user 290 userOID := &user_model.UserOpenID{UID: u.ID, URI: oid} 291 if err = user_model.AddUserOpenID(ctx, userOID); err != nil { 292 if user_model.IsErrOpenIDAlreadyUsed(err) { 293 ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplConnectOID, &form) 294 return 295 } 296 ctx.ServerError("AddUserOpenID", err) 297 return 298 } 299 300 ctx.Flash.Success(ctx.Tr("settings.add_openid_success")) 301 302 remember, _ := ctx.Session.Get("openid_signin_remember").(bool) 303 log.Trace("Session stored openid-remember: %t", remember) 304 handleSignIn(ctx, u, remember) 305 } 306 307 // RegisterOpenID shows a form to create a new user authenticated via an OpenID URI 308 func RegisterOpenID(ctx *context.Context) { 309 oid, _ := ctx.Session.Get("openid_verified_uri").(string) 310 if oid == "" { 311 ctx.Redirect(setting.AppSubURL + "/user/login/openid") 312 return 313 } 314 ctx.Data["Title"] = "OpenID signup" 315 ctx.Data["PageIsSignIn"] = true 316 ctx.Data["PageIsOpenIDRegister"] = true 317 ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp 318 ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration 319 ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha 320 ctx.Data["Captcha"] = context.GetImageCaptcha() 321 ctx.Data["CaptchaType"] = setting.Service.CaptchaType 322 ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey 323 ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey 324 ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL 325 ctx.Data["McaptchaSitekey"] = setting.Service.McaptchaSitekey 326 ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL 327 ctx.Data["OpenID"] = oid 328 userName, _ := ctx.Session.Get("openid_determined_username").(string) 329 if userName != "" { 330 ctx.Data["user_name"] = userName 331 } 332 email, _ := ctx.Session.Get("openid_determined_email").(string) 333 if email != "" { 334 ctx.Data["email"] = email 335 } 336 ctx.HTML(http.StatusOK, tplSignUpOID) 337 } 338 339 // RegisterOpenIDPost handles submission of a form to create a new user authenticated via an OpenID URI 340 func RegisterOpenIDPost(ctx *context.Context) { 341 form := web.GetForm(ctx).(*forms.SignUpOpenIDForm) 342 oid, _ := ctx.Session.Get("openid_verified_uri").(string) 343 if oid == "" { 344 ctx.Redirect(setting.AppSubURL + "/user/login/openid") 345 return 346 } 347 348 ctx.Data["Title"] = "OpenID signup" 349 ctx.Data["PageIsSignIn"] = true 350 ctx.Data["PageIsOpenIDRegister"] = true 351 ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp 352 context.SetCaptchaData(ctx) 353 ctx.Data["OpenID"] = oid 354 355 if setting.Service.AllowOnlyInternalRegistration { 356 ctx.Error(http.StatusForbidden) 357 return 358 } 359 360 if setting.Service.EnableCaptcha { 361 if err := ctx.Req.ParseForm(); err != nil { 362 ctx.ServerError("", err) 363 return 364 } 365 context.VerifyCaptcha(ctx, tplSignUpOID, form) 366 } 367 368 length := setting.MinPasswordLength 369 if length < 256 { 370 length = 256 371 } 372 password, err := util.CryptoRandomString(int64(length)) 373 if err != nil { 374 ctx.RenderWithErr(err.Error(), tplSignUpOID, form) 375 return 376 } 377 378 u := &user_model.User{ 379 Name: form.UserName, 380 Email: form.Email, 381 Passwd: password, 382 } 383 if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil, false) { 384 // error already handled 385 return 386 } 387 388 // add OpenID for the user 389 userOID := &user_model.UserOpenID{UID: u.ID, URI: oid} 390 if err = user_model.AddUserOpenID(ctx, userOID); err != nil { 391 if user_model.IsErrOpenIDAlreadyUsed(err) { 392 ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplSignUpOID, &form) 393 return 394 } 395 ctx.ServerError("AddUserOpenID", err) 396 return 397 } 398 399 if !handleUserCreated(ctx, u, nil) { 400 // error already handled 401 return 402 } 403 404 remember, _ := ctx.Session.Get("openid_signin_remember").(bool) 405 log.Trace("Session stored openid-remember: %t", remember) 406 handleSignIn(ctx, u, remember) 407 }