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  }