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