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  }