mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Refactor auth package (#17962)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,457 +0,0 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/auth/openid"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/hcaptcha"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/recaptcha"
|
||||
"code.gitea.io/gitea/modules/session"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
)
|
||||
|
||||
const (
|
||||
tplSignInOpenID base.TplName = "user/auth/signin_openid"
|
||||
tplConnectOID base.TplName = "user/auth/signup_openid_connect"
|
||||
tplSignUpOID base.TplName = "user/auth/signup_openid_register"
|
||||
)
|
||||
|
||||
// SignInOpenID render sign in page
|
||||
func SignInOpenID(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
||||
|
||||
if ctx.FormString("openid.return_to") != "" {
|
||||
signInOpenIDVerify(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// Check auto-login.
|
||||
isSucceed, err := AutoSignIn(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("AutoSignIn", err)
|
||||
return
|
||||
}
|
||||
|
||||
redirectTo := ctx.FormString("redirect_to")
|
||||
if len(redirectTo) > 0 {
|
||||
middleware.SetRedirectToCookie(ctx.Resp, redirectTo)
|
||||
} else {
|
||||
redirectTo = ctx.GetCookie("redirect_to")
|
||||
}
|
||||
|
||||
if isSucceed {
|
||||
middleware.DeleteRedirectToCookie(ctx.Resp)
|
||||
ctx.RedirectToFirst(redirectTo)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsLoginOpenID"] = true
|
||||
ctx.HTML(http.StatusOK, tplSignInOpenID)
|
||||
}
|
||||
|
||||
// Check if the given OpenID URI is allowed by blacklist/whitelist
|
||||
func allowedOpenIDURI(uri string) (err error) {
|
||||
|
||||
// In case a Whitelist is present, URI must be in it
|
||||
// in order to be accepted
|
||||
if len(setting.Service.OpenIDWhitelist) != 0 {
|
||||
for _, pat := range setting.Service.OpenIDWhitelist {
|
||||
if pat.MatchString(uri) {
|
||||
return nil // pass
|
||||
}
|
||||
}
|
||||
// must match one of this or be refused
|
||||
return fmt.Errorf("URI not allowed by whitelist")
|
||||
}
|
||||
|
||||
// A blacklist match expliclty forbids
|
||||
for _, pat := range setting.Service.OpenIDBlacklist {
|
||||
if pat.MatchString(uri) {
|
||||
return fmt.Errorf("URI forbidden by blacklist")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignInOpenIDPost response for openid sign in request
|
||||
func SignInOpenIDPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.SignInOpenIDForm)
|
||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsLoginOpenID"] = true
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplSignInOpenID)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := openid.Normalize(form.Openid)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &form)
|
||||
return
|
||||
}
|
||||
form.Openid = id
|
||||
|
||||
log.Trace("OpenID uri: " + id)
|
||||
|
||||
err = allowedOpenIDURI(id)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &form)
|
||||
return
|
||||
}
|
||||
|
||||
redirectTo := setting.AppURL + "user/login/openid"
|
||||
url, err := openid.RedirectURL(id, redirectTo, setting.AppURL)
|
||||
if err != nil {
|
||||
log.Error("Error in OpenID redirect URL: %s, %v", redirectTo, err.Error())
|
||||
ctx.RenderWithErr(fmt.Sprintf("Unable to find OpenID provider in %s", redirectTo), tplSignInOpenID, &form)
|
||||
return
|
||||
}
|
||||
|
||||
// Request optional nickname and email info
|
||||
// NOTE: change to `openid.sreg.required` to require it
|
||||
url += "&openid.ns.sreg=http%3A%2F%2Fopenid.net%2Fextensions%2Fsreg%2F1.1"
|
||||
url += "&openid.sreg.optional=nickname%2Cemail"
|
||||
|
||||
log.Trace("Form-passed openid-remember: %t", form.Remember)
|
||||
|
||||
if err := ctx.Session.Set("openid_signin_remember", form.Remember); err != nil {
|
||||
log.Error("SignInOpenIDPost: Could not set openid_signin_remember in session: %v", err)
|
||||
}
|
||||
if err := ctx.Session.Release(); err != nil {
|
||||
log.Error("SignInOpenIDPost: Unable to save changes to the session: %v", err)
|
||||
}
|
||||
|
||||
ctx.Redirect(url)
|
||||
}
|
||||
|
||||
// signInOpenIDVerify handles response from OpenID provider
|
||||
func signInOpenIDVerify(ctx *context.Context) {
|
||||
|
||||
log.Trace("Incoming call to: %s", ctx.Req.URL.String())
|
||||
|
||||
fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
|
||||
log.Trace("Full URL: %s", fullURL)
|
||||
|
||||
var id, err = openid.Verify(fullURL)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
||||
Openid: id,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Verified ID: %s", id)
|
||||
|
||||
/* Now we should seek for the user and log him in, or prompt
|
||||
* to register if not found */
|
||||
|
||||
u, err := user_model.GetUserByOpenID(id)
|
||||
if err != nil {
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
||||
Openid: id,
|
||||
})
|
||||
return
|
||||
}
|
||||
log.Error("signInOpenIDVerify: %v", err)
|
||||
}
|
||||
if u != nil {
|
||||
log.Trace("User exists, logging in")
|
||||
remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
|
||||
log.Trace("Session stored openid-remember: %t", remember)
|
||||
handleSignIn(ctx, u, remember)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("User with openid: %s does not exist, should connect or register", id)
|
||||
|
||||
parsedURL, err := url.Parse(fullURL)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
||||
Openid: id,
|
||||
})
|
||||
return
|
||||
}
|
||||
values, err := url.ParseQuery(parsedURL.RawQuery)
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
||||
Openid: id,
|
||||
})
|
||||
return
|
||||
}
|
||||
email := values.Get("openid.sreg.email")
|
||||
nickname := values.Get("openid.sreg.nickname")
|
||||
|
||||
log.Trace("User has email=%s and nickname=%s", email, nickname)
|
||||
|
||||
if email != "" {
|
||||
u, err = user_model.GetUserByEmail(email)
|
||||
if err != nil {
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
||||
Openid: id,
|
||||
})
|
||||
return
|
||||
}
|
||||
log.Error("signInOpenIDVerify: %v", err)
|
||||
}
|
||||
if u != nil {
|
||||
log.Trace("Local user %s has OpenID provided email %s", u.LowerName, email)
|
||||
}
|
||||
}
|
||||
|
||||
if u == nil && nickname != "" {
|
||||
u, _ = user_model.GetUserByName(nickname)
|
||||
if err != nil {
|
||||
if !user_model.IsErrUserNotExist(err) {
|
||||
ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
||||
Openid: id,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if u != nil {
|
||||
log.Trace("Local user %s has OpenID provided nickname %s", u.LowerName, nickname)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
|
||||
ctx.ServerError("RegenerateSession", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ctx.Session.Set("openid_verified_uri", id); err != nil {
|
||||
log.Error("signInOpenIDVerify: Could not set openid_verified_uri in session: %v", err)
|
||||
}
|
||||
if err := ctx.Session.Set("openid_determined_email", email); err != nil {
|
||||
log.Error("signInOpenIDVerify: Could not set openid_determined_email in session: %v", err)
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
nickname = u.LowerName
|
||||
}
|
||||
|
||||
if err := ctx.Session.Set("openid_determined_username", nickname); err != nil {
|
||||
log.Error("signInOpenIDVerify: Could not set openid_determined_username in session: %v", err)
|
||||
}
|
||||
if err := ctx.Session.Release(); err != nil {
|
||||
log.Error("signInOpenIDVerify: Unable to save changes to the session: %v", err)
|
||||
}
|
||||
|
||||
if u != nil || !setting.Service.EnableOpenIDSignUp || setting.Service.AllowOnlyInternalRegistration {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/openid/connect")
|
||||
} else {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/openid/register")
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectOpenID shows a form to connect an OpenID URI to an existing account
|
||||
func ConnectOpenID(ctx *context.Context) {
|
||||
oid, _ := ctx.Session.Get("openid_verified_uri").(string)
|
||||
if oid == "" {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/login/openid")
|
||||
return
|
||||
}
|
||||
ctx.Data["Title"] = "OpenID connect"
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsOpenIDConnect"] = true
|
||||
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
|
||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||
ctx.Data["OpenID"] = oid
|
||||
userName, _ := ctx.Session.Get("openid_determined_username").(string)
|
||||
if userName != "" {
|
||||
ctx.Data["user_name"] = userName
|
||||
}
|
||||
ctx.HTML(http.StatusOK, tplConnectOID)
|
||||
}
|
||||
|
||||
// ConnectOpenIDPost handles submission of a form to connect an OpenID URI to an existing account
|
||||
func ConnectOpenIDPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.ConnectOpenIDForm)
|
||||
oid, _ := ctx.Session.Get("openid_verified_uri").(string)
|
||||
if oid == "" {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/login/openid")
|
||||
return
|
||||
}
|
||||
ctx.Data["Title"] = "OpenID connect"
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsOpenIDConnect"] = true
|
||||
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
|
||||
ctx.Data["OpenID"] = oid
|
||||
|
||||
u, _, err := auth.UserSignIn(form.UserName, form.Password)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplConnectOID, &form)
|
||||
} else {
|
||||
ctx.ServerError("ConnectOpenIDPost", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// add OpenID for the user
|
||||
userOID := &user_model.UserOpenID{UID: u.ID, URI: oid}
|
||||
if err = user_model.AddUserOpenID(userOID); err != nil {
|
||||
if user_model.IsErrOpenIDAlreadyUsed(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplConnectOID, &form)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("AddUserOpenID", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
|
||||
|
||||
remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
|
||||
log.Trace("Session stored openid-remember: %t", remember)
|
||||
handleSignIn(ctx, u, remember)
|
||||
}
|
||||
|
||||
// RegisterOpenID shows a form to create a new user authenticated via an OpenID URI
|
||||
func RegisterOpenID(ctx *context.Context) {
|
||||
oid, _ := ctx.Session.Get("openid_verified_uri").(string)
|
||||
if oid == "" {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/login/openid")
|
||||
return
|
||||
}
|
||||
ctx.Data["Title"] = "OpenID signup"
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsOpenIDRegister"] = true
|
||||
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
|
||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
|
||||
ctx.Data["Captcha"] = context.GetImageCaptcha()
|
||||
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||
ctx.Data["OpenID"] = oid
|
||||
userName, _ := ctx.Session.Get("openid_determined_username").(string)
|
||||
if userName != "" {
|
||||
ctx.Data["user_name"] = userName
|
||||
}
|
||||
email, _ := ctx.Session.Get("openid_determined_email").(string)
|
||||
if email != "" {
|
||||
ctx.Data["email"] = email
|
||||
}
|
||||
ctx.HTML(http.StatusOK, tplSignUpOID)
|
||||
}
|
||||
|
||||
// RegisterOpenIDPost handles submission of a form to create a new user authenticated via an OpenID URI
|
||||
func RegisterOpenIDPost(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.SignUpOpenIDForm)
|
||||
oid, _ := ctx.Session.Get("openid_verified_uri").(string)
|
||||
if oid == "" {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/login/openid")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = "OpenID signup"
|
||||
ctx.Data["PageIsSignIn"] = true
|
||||
ctx.Data["PageIsOpenIDRegister"] = true
|
||||
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
|
||||
ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
|
||||
ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
|
||||
ctx.Data["Captcha"] = context.GetImageCaptcha()
|
||||
ctx.Data["CaptchaType"] = setting.Service.CaptchaType
|
||||
ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
|
||||
ctx.Data["HcaptchaSitekey"] = setting.Service.HcaptchaSitekey
|
||||
ctx.Data["OpenID"] = oid
|
||||
|
||||
if setting.Service.AllowOnlyInternalRegistration {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if setting.Service.EnableCaptcha {
|
||||
var valid bool
|
||||
var err error
|
||||
switch setting.Service.CaptchaType {
|
||||
case setting.ImageCaptcha:
|
||||
valid = context.GetImageCaptcha().VerifyReq(ctx.Req)
|
||||
case setting.ReCaptcha:
|
||||
if err := ctx.Req.ParseForm(); err != nil {
|
||||
ctx.ServerError("", err)
|
||||
return
|
||||
}
|
||||
valid, err = recaptcha.Verify(ctx, form.GRecaptchaResponse)
|
||||
case setting.HCaptcha:
|
||||
if err := ctx.Req.ParseForm(); err != nil {
|
||||
ctx.ServerError("", err)
|
||||
return
|
||||
}
|
||||
valid, err = hcaptcha.Verify(ctx, form.HcaptchaResponse)
|
||||
default:
|
||||
ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Debug("%s", err.Error())
|
||||
}
|
||||
|
||||
if !valid {
|
||||
ctx.Data["Err_Captcha"] = true
|
||||
ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
length := setting.MinPasswordLength
|
||||
if length < 256 {
|
||||
length = 256
|
||||
}
|
||||
password, err := util.RandomString(int64(length))
|
||||
if err != nil {
|
||||
ctx.RenderWithErr(err.Error(), tplSignUpOID, form)
|
||||
return
|
||||
}
|
||||
|
||||
u := &user_model.User{
|
||||
Name: form.UserName,
|
||||
Email: form.Email,
|
||||
Passwd: password,
|
||||
IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm),
|
||||
}
|
||||
if !createUserInContext(ctx, tplSignUpOID, form, u, nil, false) {
|
||||
// error already handled
|
||||
return
|
||||
}
|
||||
|
||||
// add OpenID for the user
|
||||
userOID := &user_model.UserOpenID{UID: u.ID, URI: oid}
|
||||
if err = user_model.AddUserOpenID(userOID); err != nil {
|
||||
if user_model.IsErrOpenIDAlreadyUsed(err) {
|
||||
ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplSignUpOID, &form)
|
||||
return
|
||||
}
|
||||
ctx.ServerError("AddUserOpenID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !handleUserCreated(ctx, u, nil) {
|
||||
// error already handled
|
||||
return
|
||||
}
|
||||
|
||||
remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
|
||||
log.Trace("Session stored openid-remember: %t", remember)
|
||||
handleSignIn(ctx, u, remember)
|
||||
}
|
@@ -1,752 +0,0 @@
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/login"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
"github.com/golang-jwt/jwt"
|
||||
)
|
||||
|
||||
const (
|
||||
tplGrantAccess base.TplName = "user/auth/grant"
|
||||
tplGrantError base.TplName = "user/auth/grant_error"
|
||||
)
|
||||
|
||||
// TODO move error and responses to SDK or models
|
||||
|
||||
// AuthorizeErrorCode represents an error code specified in RFC 6749
|
||||
type AuthorizeErrorCode string
|
||||
|
||||
const (
|
||||
// ErrorCodeInvalidRequest represents the according error in RFC 6749
|
||||
ErrorCodeInvalidRequest AuthorizeErrorCode = "invalid_request"
|
||||
// ErrorCodeUnauthorizedClient represents the according error in RFC 6749
|
||||
ErrorCodeUnauthorizedClient AuthorizeErrorCode = "unauthorized_client"
|
||||
// ErrorCodeAccessDenied represents the according error in RFC 6749
|
||||
ErrorCodeAccessDenied AuthorizeErrorCode = "access_denied"
|
||||
// ErrorCodeUnsupportedResponseType represents the according error in RFC 6749
|
||||
ErrorCodeUnsupportedResponseType AuthorizeErrorCode = "unsupported_response_type"
|
||||
// ErrorCodeInvalidScope represents the according error in RFC 6749
|
||||
ErrorCodeInvalidScope AuthorizeErrorCode = "invalid_scope"
|
||||
// ErrorCodeServerError represents the according error in RFC 6749
|
||||
ErrorCodeServerError AuthorizeErrorCode = "server_error"
|
||||
// ErrorCodeTemporaryUnavailable represents the according error in RFC 6749
|
||||
ErrorCodeTemporaryUnavailable AuthorizeErrorCode = "temporarily_unavailable"
|
||||
)
|
||||
|
||||
// AuthorizeError represents an error type specified in RFC 6749
|
||||
type AuthorizeError struct {
|
||||
ErrorCode AuthorizeErrorCode `json:"error" form:"error"`
|
||||
ErrorDescription string
|
||||
State string
|
||||
}
|
||||
|
||||
// Error returns the error message
|
||||
func (err AuthorizeError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
|
||||
}
|
||||
|
||||
// AccessTokenErrorCode represents an error code specified in RFC 6749
|
||||
type AccessTokenErrorCode string
|
||||
|
||||
const (
|
||||
// AccessTokenErrorCodeInvalidRequest represents an error code specified in RFC 6749
|
||||
AccessTokenErrorCodeInvalidRequest AccessTokenErrorCode = "invalid_request"
|
||||
// AccessTokenErrorCodeInvalidClient represents an error code specified in RFC 6749
|
||||
AccessTokenErrorCodeInvalidClient = "invalid_client"
|
||||
// AccessTokenErrorCodeInvalidGrant represents an error code specified in RFC 6749
|
||||
AccessTokenErrorCodeInvalidGrant = "invalid_grant"
|
||||
// AccessTokenErrorCodeUnauthorizedClient represents an error code specified in RFC 6749
|
||||
AccessTokenErrorCodeUnauthorizedClient = "unauthorized_client"
|
||||
// AccessTokenErrorCodeUnsupportedGrantType represents an error code specified in RFC 6749
|
||||
AccessTokenErrorCodeUnsupportedGrantType = "unsupported_grant_type"
|
||||
// AccessTokenErrorCodeInvalidScope represents an error code specified in RFC 6749
|
||||
AccessTokenErrorCodeInvalidScope = "invalid_scope"
|
||||
)
|
||||
|
||||
// AccessTokenError represents an error response specified in RFC 6749
|
||||
type AccessTokenError struct {
|
||||
ErrorCode AccessTokenErrorCode `json:"error" form:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
// Error returns the error message
|
||||
func (err AccessTokenError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
|
||||
}
|
||||
|
||||
// TokenType specifies the kind of token
|
||||
type TokenType string
|
||||
|
||||
const (
|
||||
// TokenTypeBearer represents a token type specified in RFC 6749
|
||||
TokenTypeBearer TokenType = "bearer"
|
||||
// TokenTypeMAC represents a token type specified in RFC 6749
|
||||
TokenTypeMAC = "mac"
|
||||
)
|
||||
|
||||
// AccessTokenResponse represents a successful access token response
|
||||
type AccessTokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType TokenType `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
IDToken string `json:"id_token,omitempty"`
|
||||
}
|
||||
|
||||
func newAccessTokenResponse(grant *login.OAuth2Grant, serverKey, clientKey oauth2.JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) {
|
||||
if setting.OAuth2.InvalidateRefreshTokens {
|
||||
if err := grant.IncreaseCounter(); err != nil {
|
||||
return nil, &AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidGrant,
|
||||
ErrorDescription: "cannot increase the grant counter",
|
||||
}
|
||||
}
|
||||
}
|
||||
// generate access token to access the API
|
||||
expirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime)
|
||||
accessToken := &oauth2.Token{
|
||||
GrantID: grant.ID,
|
||||
Type: oauth2.TypeAccessToken,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: expirationDate.AsTime().Unix(),
|
||||
},
|
||||
}
|
||||
signedAccessToken, err := accessToken.SignToken(serverKey)
|
||||
if err != nil {
|
||||
return nil, &AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot sign token",
|
||||
}
|
||||
}
|
||||
|
||||
// generate refresh token to request an access token after it expired later
|
||||
refreshExpirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime().Unix()
|
||||
refreshToken := &oauth2.Token{
|
||||
GrantID: grant.ID,
|
||||
Counter: grant.Counter,
|
||||
Type: oauth2.TypeRefreshToken,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: refreshExpirationDate,
|
||||
},
|
||||
}
|
||||
signedRefreshToken, err := refreshToken.SignToken(serverKey)
|
||||
if err != nil {
|
||||
return nil, &AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot sign token",
|
||||
}
|
||||
}
|
||||
|
||||
// generate OpenID Connect id_token
|
||||
signedIDToken := ""
|
||||
if grant.ScopeContains("openid") {
|
||||
app, err := login.GetOAuth2ApplicationByID(grant.ApplicationID)
|
||||
if err != nil {
|
||||
return nil, &AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot find application",
|
||||
}
|
||||
}
|
||||
user, err := user_model.GetUserByID(grant.UserID)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
return nil, &AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot find user",
|
||||
}
|
||||
}
|
||||
log.Error("Error loading user: %v", err)
|
||||
return nil, &AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "server error",
|
||||
}
|
||||
}
|
||||
|
||||
idToken := &oauth2.OIDCToken{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: expirationDate.AsTime().Unix(),
|
||||
Issuer: setting.AppURL,
|
||||
Audience: app.ClientID,
|
||||
Subject: fmt.Sprint(grant.UserID),
|
||||
},
|
||||
Nonce: grant.Nonce,
|
||||
}
|
||||
if grant.ScopeContains("profile") {
|
||||
idToken.Name = user.FullName
|
||||
idToken.PreferredUsername = user.Name
|
||||
idToken.Profile = user.HTMLURL()
|
||||
idToken.Picture = user.AvatarLink()
|
||||
idToken.Website = user.Website
|
||||
idToken.Locale = user.Language
|
||||
idToken.UpdatedAt = user.UpdatedUnix
|
||||
}
|
||||
if grant.ScopeContains("email") {
|
||||
idToken.Email = user.Email
|
||||
idToken.EmailVerified = user.IsActive
|
||||
}
|
||||
if grant.ScopeContains("groups") {
|
||||
groups, err := getOAuthGroupsForUser(user)
|
||||
if err != nil {
|
||||
log.Error("Error getting groups: %v", err)
|
||||
return nil, &AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "server error",
|
||||
}
|
||||
}
|
||||
idToken.Groups = groups
|
||||
}
|
||||
|
||||
signedIDToken, err = idToken.SignToken(clientKey)
|
||||
if err != nil {
|
||||
return nil, &AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot sign token",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &AccessTokenResponse{
|
||||
AccessToken: signedAccessToken,
|
||||
TokenType: TokenTypeBearer,
|
||||
ExpiresIn: setting.OAuth2.AccessTokenExpirationTime,
|
||||
RefreshToken: signedRefreshToken,
|
||||
IDToken: signedIDToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type userInfoResponse struct {
|
||||
Sub string `json:"sub"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"preferred_username"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture"`
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
|
||||
// InfoOAuth manages request for userinfo endpoint
|
||||
func InfoOAuth(ctx *context.Context) {
|
||||
if ctx.User == nil || ctx.Data["AuthedMethod"] != (&auth.OAuth2{}).Name() {
|
||||
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
|
||||
ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
|
||||
return
|
||||
}
|
||||
|
||||
response := &userInfoResponse{
|
||||
Sub: fmt.Sprint(ctx.User.ID),
|
||||
Name: ctx.User.FullName,
|
||||
Username: ctx.User.Name,
|
||||
Email: ctx.User.Email,
|
||||
Picture: ctx.User.AvatarLink(),
|
||||
}
|
||||
|
||||
groups, err := getOAuthGroupsForUser(ctx.User)
|
||||
if err != nil {
|
||||
ctx.ServerError("Oauth groups for user", err)
|
||||
return
|
||||
}
|
||||
response.Groups = groups
|
||||
|
||||
ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// returns a list of "org" and "org:team" strings,
|
||||
// that the given user is a part of.
|
||||
func getOAuthGroupsForUser(user *user_model.User) ([]string, error) {
|
||||
orgs, err := models.GetUserOrgsList(user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserOrgList: %v", err)
|
||||
}
|
||||
|
||||
var groups []string
|
||||
for _, org := range orgs {
|
||||
groups = append(groups, org.Name)
|
||||
teams, err := org.LoadTeams()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("LoadTeams: %v", err)
|
||||
}
|
||||
for _, team := range teams {
|
||||
if team.IsMember(user.ID) {
|
||||
groups = append(groups, org.Name+":"+team.LowerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// IntrospectOAuth introspects an oauth token
|
||||
func IntrospectOAuth(ctx *context.Context) {
|
||||
if ctx.User == nil {
|
||||
ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
|
||||
ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
|
||||
return
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Active bool `json:"active"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*forms.IntrospectTokenForm)
|
||||
token, err := oauth2.ParseToken(form.Token, oauth2.DefaultSigningKey)
|
||||
if err == nil {
|
||||
if token.Valid() == nil {
|
||||
grant, err := login.GetOAuth2GrantByID(token.GrantID)
|
||||
if err == nil && grant != nil {
|
||||
app, err := login.GetOAuth2ApplicationByID(grant.ApplicationID)
|
||||
if err == nil && app != nil {
|
||||
response.Active = true
|
||||
response.Scope = grant.Scope
|
||||
response.Issuer = setting.AppURL
|
||||
response.Audience = app.ClientID
|
||||
response.Subject = fmt.Sprint(grant.UserID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// AuthorizeOAuth manages authorize requests
|
||||
func AuthorizeOAuth(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.AuthorizationForm)
|
||||
errs := binding.Errors{}
|
||||
errs = form.Validate(ctx.Req, errs)
|
||||
if len(errs) > 0 {
|
||||
errstring := ""
|
||||
for _, e := range errs {
|
||||
errstring += e.Error() + "\n"
|
||||
}
|
||||
ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring))
|
||||
return
|
||||
}
|
||||
|
||||
app, err := login.GetOAuth2ApplicationByClientID(form.ClientID)
|
||||
if err != nil {
|
||||
if login.IsErrOauthClientIDInvalid(err) {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeUnauthorizedClient,
|
||||
ErrorDescription: "Client ID not registered",
|
||||
State: form.State,
|
||||
}, "")
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetOAuth2ApplicationByClientID", err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := user_model.GetUserByID(app.UID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUserByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !app.ContainsRedirectURI(form.RedirectURI) {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeInvalidRequest,
|
||||
ErrorDescription: "Unregistered Redirect URI",
|
||||
State: form.State,
|
||||
}, "")
|
||||
return
|
||||
}
|
||||
|
||||
if form.ResponseType != "code" {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeUnsupportedResponseType,
|
||||
ErrorDescription: "Only code response type is supported.",
|
||||
State: form.State,
|
||||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
|
||||
// pkce support
|
||||
switch form.CodeChallengeMethod {
|
||||
case "S256":
|
||||
case "plain":
|
||||
if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeServerError,
|
||||
ErrorDescription: "cannot set code challenge method",
|
||||
State: form.State,
|
||||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeServerError,
|
||||
ErrorDescription: "cannot set code challenge",
|
||||
State: form.State,
|
||||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
// Here we're just going to try to release the session early
|
||||
if err := ctx.Session.Release(); err != nil {
|
||||
// we'll tolerate errors here as they *should* get saved elsewhere
|
||||
log.Error("Unable to save changes to the session: %v", err)
|
||||
}
|
||||
case "":
|
||||
break
|
||||
default:
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeInvalidRequest,
|
||||
ErrorDescription: "unsupported code challenge method",
|
||||
State: form.State,
|
||||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
|
||||
grant, err := app.GetGrantByUserID(ctx.User.ID)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect if user already granted access
|
||||
if grant != nil {
|
||||
code, err := grant.GenerateNewAuthorizationCode(form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
redirect, err := code.GenerateRedirectURI(form.State)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
// Update nonce to reflect the new session
|
||||
if len(form.Nonce) > 0 {
|
||||
err := grant.SetNonce(form.Nonce)
|
||||
if err != nil {
|
||||
log.Error("Unable to update nonce: %v", err)
|
||||
}
|
||||
}
|
||||
ctx.Redirect(redirect.String(), 302)
|
||||
return
|
||||
}
|
||||
|
||||
// show authorize page to grant access
|
||||
ctx.Data["Application"] = app
|
||||
ctx.Data["RedirectURI"] = form.RedirectURI
|
||||
ctx.Data["State"] = form.State
|
||||
ctx.Data["Scope"] = form.Scope
|
||||
ctx.Data["Nonce"] = form.Nonce
|
||||
ctx.Data["ApplicationUserLink"] = "<a href=\"" + html.EscapeString(user.HTMLURL()) + "\">@" + html.EscapeString(user.Name) + "</a>"
|
||||
ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + html.EscapeString(form.RedirectURI) + "</strong>"
|
||||
// TODO document SESSION <=> FORM
|
||||
err = ctx.Session.Set("client_id", app.ClientID)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
err = ctx.Session.Set("redirect_uri", form.RedirectURI)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
err = ctx.Session.Set("state", form.State)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
// Here we're just going to try to release the session early
|
||||
if err := ctx.Session.Release(); err != nil {
|
||||
// we'll tolerate errors here as they *should* get saved elsewhere
|
||||
log.Error("Unable to save changes to the session: %v", err)
|
||||
}
|
||||
ctx.HTML(http.StatusOK, tplGrantAccess)
|
||||
}
|
||||
|
||||
// GrantApplicationOAuth manages the post request submitted when a user grants access to an application
|
||||
func GrantApplicationOAuth(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.GrantApplicationForm)
|
||||
if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State ||
|
||||
ctx.Session.Get("redirect_uri") != form.RedirectURI {
|
||||
ctx.Error(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
app, err := login.GetOAuth2ApplicationByClientID(form.ClientID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetOAuth2ApplicationByClientID", err)
|
||||
return
|
||||
}
|
||||
grant, err := app.CreateGrant(ctx.User.ID, form.Scope)
|
||||
if err != nil {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
State: form.State,
|
||||
ErrorDescription: "cannot create grant for user",
|
||||
ErrorCode: ErrorCodeServerError,
|
||||
}, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
if len(form.Nonce) > 0 {
|
||||
err := grant.SetNonce(form.Nonce)
|
||||
if err != nil {
|
||||
log.Error("Unable to update nonce: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var codeChallenge, codeChallengeMethod string
|
||||
codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string)
|
||||
codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string)
|
||||
|
||||
code, err := grant.GenerateNewAuthorizationCode(form.RedirectURI, codeChallenge, codeChallengeMethod)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
redirect, err := code.GenerateRedirectURI(form.State)
|
||||
if err != nil {
|
||||
handleServerError(ctx, form.State, form.RedirectURI)
|
||||
return
|
||||
}
|
||||
ctx.Redirect(redirect.String(), 302)
|
||||
}
|
||||
|
||||
// OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
|
||||
func OIDCWellKnown(ctx *context.Context) {
|
||||
t := ctx.Render.TemplateLookup("user/auth/oidc_wellknown")
|
||||
ctx.Resp.Header().Set("Content-Type", "application/json")
|
||||
ctx.Data["SigningKey"] = oauth2.DefaultSigningKey
|
||||
if err := t.Execute(ctx.Resp, ctx.Data); err != nil {
|
||||
log.Error("%v", err)
|
||||
ctx.Error(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// OIDCKeys generates the JSON Web Key Set
|
||||
func OIDCKeys(ctx *context.Context) {
|
||||
jwk, err := oauth2.DefaultSigningKey.ToJWK()
|
||||
if err != nil {
|
||||
log.Error("Error converting signing key to JWK: %v", err)
|
||||
ctx.Error(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
jwk["use"] = "sig"
|
||||
|
||||
jwks := map[string][]map[string]string{
|
||||
"keys": {
|
||||
jwk,
|
||||
},
|
||||
}
|
||||
|
||||
ctx.Resp.Header().Set("Content-Type", "application/json")
|
||||
enc := json.NewEncoder(ctx.Resp)
|
||||
if err := enc.Encode(jwks); err != nil {
|
||||
log.Error("Failed to encode representation as json. Error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// AccessTokenOAuth manages all access token requests by the client
|
||||
func AccessTokenOAuth(ctx *context.Context) {
|
||||
form := *web.GetForm(ctx).(*forms.AccessTokenForm)
|
||||
if form.ClientID == "" {
|
||||
authHeader := ctx.Req.Header.Get("Authorization")
|
||||
authContent := strings.SplitN(authHeader, " ", 2)
|
||||
if len(authContent) == 2 && authContent[0] == "Basic" {
|
||||
payload, err := base64.StdEncoding.DecodeString(authContent[1])
|
||||
if err != nil {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot parse basic auth header",
|
||||
})
|
||||
return
|
||||
}
|
||||
pair := strings.SplitN(string(payload), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot parse basic auth header",
|
||||
})
|
||||
return
|
||||
}
|
||||
form.ClientID = pair[0]
|
||||
form.ClientSecret = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
serverKey := oauth2.DefaultSigningKey
|
||||
clientKey := serverKey
|
||||
if serverKey.IsSymmetric() {
|
||||
var err error
|
||||
clientKey, err = oauth2.CreateJWTSigningKey(serverKey.SigningMethod().Alg(), []byte(form.ClientSecret))
|
||||
if err != nil {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "Error creating signing key",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch form.GrantType {
|
||||
case "refresh_token":
|
||||
handleRefreshToken(ctx, form, serverKey, clientKey)
|
||||
case "authorization_code":
|
||||
handleAuthorizationCode(ctx, form, serverKey, clientKey)
|
||||
default:
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeUnsupportedGrantType,
|
||||
ErrorDescription: "Only refresh_token or authorization_code grant type is supported",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) {
|
||||
token, err := oauth2.ParseToken(form.RefreshToken, serverKey)
|
||||
if err != nil {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
|
||||
ErrorDescription: "client is not authorized",
|
||||
})
|
||||
return
|
||||
}
|
||||
// get grant before increasing counter
|
||||
grant, err := login.GetOAuth2GrantByID(token.GrantID)
|
||||
if err != nil || grant == nil {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidGrant,
|
||||
ErrorDescription: "grant does not exist",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// check if token got already used
|
||||
if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
|
||||
ErrorDescription: "token was already used",
|
||||
})
|
||||
log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID)
|
||||
return
|
||||
}
|
||||
accessToken, tokenErr := newAccessTokenResponse(grant, serverKey, clientKey)
|
||||
if tokenErr != nil {
|
||||
handleAccessTokenError(ctx, *tokenErr)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, accessToken)
|
||||
}
|
||||
|
||||
func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) {
|
||||
app, err := login.GetOAuth2ApplicationByClientID(form.ClientID)
|
||||
if err != nil {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidClient,
|
||||
ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !app.ValidateClientSecret([]byte(form.ClientSecret)) {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
|
||||
ErrorDescription: "client is not authorized",
|
||||
})
|
||||
return
|
||||
}
|
||||
if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
|
||||
ErrorDescription: "client is not authorized",
|
||||
})
|
||||
return
|
||||
}
|
||||
authorizationCode, err := login.GetOAuth2AuthorizationByCode(form.Code)
|
||||
if err != nil || authorizationCode == nil {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
|
||||
ErrorDescription: "client is not authorized",
|
||||
})
|
||||
return
|
||||
}
|
||||
// check if code verifier authorizes the client, PKCE support
|
||||
if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
|
||||
ErrorDescription: "client is not authorized",
|
||||
})
|
||||
return
|
||||
}
|
||||
// check if granted for this application
|
||||
if authorizationCode.Grant.ApplicationID != app.ID {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidGrant,
|
||||
ErrorDescription: "invalid grant",
|
||||
})
|
||||
return
|
||||
}
|
||||
// remove token from database to deny duplicate usage
|
||||
if err := authorizationCode.Invalidate(); err != nil {
|
||||
handleAccessTokenError(ctx, AccessTokenError{
|
||||
ErrorCode: AccessTokenErrorCodeInvalidRequest,
|
||||
ErrorDescription: "cannot proceed your request",
|
||||
})
|
||||
}
|
||||
resp, tokenErr := newAccessTokenResponse(authorizationCode.Grant, serverKey, clientKey)
|
||||
if tokenErr != nil {
|
||||
handleAccessTokenError(ctx, *tokenErr)
|
||||
return
|
||||
}
|
||||
// send successful response
|
||||
ctx.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func handleAccessTokenError(ctx *context.Context, acErr AccessTokenError) {
|
||||
ctx.JSON(http.StatusBadRequest, acErr)
|
||||
}
|
||||
|
||||
func handleServerError(ctx *context.Context, state, redirectURI string) {
|
||||
handleAuthorizeError(ctx, AuthorizeError{
|
||||
ErrorCode: ErrorCodeServerError,
|
||||
ErrorDescription: "A server error occurred",
|
||||
State: state,
|
||||
}, redirectURI)
|
||||
}
|
||||
|
||||
func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) {
|
||||
if redirectURI == "" {
|
||||
log.Warn("Authorization failed: %v", authErr.ErrorDescription)
|
||||
ctx.Data["Error"] = authErr
|
||||
ctx.HTML(400, tplGrantError)
|
||||
return
|
||||
}
|
||||
redirect, err := url.Parse(redirectURI)
|
||||
if err != nil {
|
||||
ctx.ServerError("url.Parse", err)
|
||||
return
|
||||
}
|
||||
q := redirect.Query()
|
||||
q.Set("error", string(authErr.ErrorCode))
|
||||
q.Set("error_description", authErr.ErrorDescription)
|
||||
q.Set("state", authErr.State)
|
||||
redirect.RawQuery = q.Encode()
|
||||
ctx.Redirect(redirect.String(), 302)
|
||||
}
|
@@ -1,76 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/login"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func createAndParseToken(t *testing.T, grant *login.OAuth2Grant) *oauth2.OIDCToken {
|
||||
signingKey, err := oauth2.CreateJWTSigningKey("HS256", make([]byte, 32))
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, signingKey)
|
||||
|
||||
response, terr := newAccessTokenResponse(grant, signingKey, signingKey)
|
||||
assert.Nil(t, terr)
|
||||
assert.NotNil(t, response)
|
||||
|
||||
parsedToken, err := jwt.ParseWithClaims(response.IDToken, &oauth2.OIDCToken{}, func(token *jwt.Token) (interface{}, error) {
|
||||
assert.NotNil(t, token.Method)
|
||||
assert.Equal(t, signingKey.SigningMethod().Alg(), token.Method.Alg())
|
||||
return signingKey.VerifyKey(), nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, parsedToken.Valid)
|
||||
|
||||
oidcToken, ok := parsedToken.Claims.(*oauth2.OIDCToken)
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, oidcToken)
|
||||
|
||||
return oidcToken
|
||||
}
|
||||
|
||||
func TestNewAccessTokenResponse_OIDCToken(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
grants, err := login.GetOAuth2GrantsByUserID(3)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, grants, 1)
|
||||
|
||||
// Scopes: openid
|
||||
oidcToken := createAndParseToken(t, grants[0])
|
||||
assert.Empty(t, oidcToken.Name)
|
||||
assert.Empty(t, oidcToken.PreferredUsername)
|
||||
assert.Empty(t, oidcToken.Profile)
|
||||
assert.Empty(t, oidcToken.Picture)
|
||||
assert.Empty(t, oidcToken.Website)
|
||||
assert.Empty(t, oidcToken.UpdatedAt)
|
||||
assert.Empty(t, oidcToken.Email)
|
||||
assert.False(t, oidcToken.EmailVerified)
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
|
||||
grants, err = login.GetOAuth2GrantsByUserID(user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, grants, 1)
|
||||
|
||||
// Scopes: openid profile email
|
||||
oidcToken = createAndParseToken(t, grants[0])
|
||||
assert.Equal(t, user.FullName, oidcToken.Name)
|
||||
assert.Equal(t, user.Name, oidcToken.PreferredUsername)
|
||||
assert.Equal(t, user.HTMLURL(), oidcToken.Profile)
|
||||
assert.Equal(t, user.AvatarLink(), oidcToken.Picture)
|
||||
assert.Equal(t, user.Website, oidcToken.Website)
|
||||
assert.Equal(t, user.UpdatedUnix, oidcToken.UpdatedAt)
|
||||
assert.Equal(t, user.Email, oidcToken.Email)
|
||||
assert.Equal(t, user.IsActive, oidcToken.EmailVerified)
|
||||
}
|
@@ -9,7 +9,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/login"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -93,12 +93,12 @@ func loadApplicationsData(ctx *context.Context) {
|
||||
ctx.Data["Tokens"] = tokens
|
||||
ctx.Data["EnableOAuth2"] = setting.OAuth2.Enable
|
||||
if setting.OAuth2.Enable {
|
||||
ctx.Data["Applications"], err = login.GetOAuth2ApplicationsByUserID(ctx.User.ID)
|
||||
ctx.Data["Applications"], err = auth.GetOAuth2ApplicationsByUserID(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetOAuth2ApplicationsByUserID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Grants"], err = login.GetOAuth2GrantsByUserID(ctx.User.ID)
|
||||
ctx.Data["Grants"], err = auth.GetOAuth2GrantsByUserID(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetOAuth2GrantsByUserID", err)
|
||||
return
|
||||
|
@@ -216,7 +216,6 @@ func KeysPost(ctx *context.Context) {
|
||||
|
||||
// DeleteKey response for delete user's SSH/GPG key
|
||||
func DeleteKey(ctx *context.Context) {
|
||||
|
||||
switch ctx.FormString("type") {
|
||||
case "gpg":
|
||||
if err := asymkey_model.DeleteGPGKey(ctx.User, ctx.FormInt64("id")); err != nil {
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/login"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
@@ -34,7 +34,7 @@ func OAuthApplicationsPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
// TODO validate redirect URI
|
||||
app, err := login.CreateOAuth2Application(login.CreateOAuth2ApplicationOptions{
|
||||
app, err := auth.CreateOAuth2Application(auth.CreateOAuth2ApplicationOptions{
|
||||
Name: form.Name,
|
||||
RedirectURIs: []string{form.RedirectURI},
|
||||
UserID: ctx.User.ID,
|
||||
@@ -67,7 +67,7 @@ func OAuthApplicationsEdit(ctx *context.Context) {
|
||||
}
|
||||
// TODO validate redirect URI
|
||||
var err error
|
||||
if ctx.Data["App"], err = login.UpdateOAuth2Application(login.UpdateOAuth2ApplicationOptions{
|
||||
if ctx.Data["App"], err = auth.UpdateOAuth2Application(auth.UpdateOAuth2ApplicationOptions{
|
||||
ID: ctx.ParamsInt64("id"),
|
||||
Name: form.Name,
|
||||
RedirectURIs: []string{form.RedirectURI},
|
||||
@@ -85,9 +85,9 @@ func OAuthApplicationsRegenerateSecret(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsApplications"] = true
|
||||
|
||||
app, err := login.GetOAuth2ApplicationByID(ctx.ParamsInt64("id"))
|
||||
app, err := auth.GetOAuth2ApplicationByID(ctx.ParamsInt64("id"))
|
||||
if err != nil {
|
||||
if login.IsErrOAuthApplicationNotFound(err) {
|
||||
if auth.IsErrOAuthApplicationNotFound(err) {
|
||||
ctx.NotFound("Application not found", err)
|
||||
return
|
||||
}
|
||||
@@ -110,9 +110,9 @@ func OAuthApplicationsRegenerateSecret(ctx *context.Context) {
|
||||
|
||||
// OAuth2ApplicationShow displays the given application
|
||||
func OAuth2ApplicationShow(ctx *context.Context) {
|
||||
app, err := login.GetOAuth2ApplicationByID(ctx.ParamsInt64("id"))
|
||||
app, err := auth.GetOAuth2ApplicationByID(ctx.ParamsInt64("id"))
|
||||
if err != nil {
|
||||
if login.IsErrOAuthApplicationNotFound(err) {
|
||||
if auth.IsErrOAuthApplicationNotFound(err) {
|
||||
ctx.NotFound("Application not found", err)
|
||||
return
|
||||
}
|
||||
@@ -129,7 +129,7 @@ func OAuth2ApplicationShow(ctx *context.Context) {
|
||||
|
||||
// DeleteOAuth2Application deletes the given oauth2 application
|
||||
func DeleteOAuth2Application(ctx *context.Context) {
|
||||
if err := login.DeleteOAuth2Application(ctx.FormInt64("id"), ctx.User.ID); err != nil {
|
||||
if err := auth.DeleteOAuth2Application(ctx.FormInt64("id"), ctx.User.ID); err != nil {
|
||||
ctx.ServerError("DeleteOAuth2Application", err)
|
||||
return
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func RevokeOAuth2Grant(ctx *context.Context) {
|
||||
ctx.ServerError("RevokeOAuth2Grant", fmt.Errorf("user id or grant id is zero"))
|
||||
return
|
||||
}
|
||||
if err := login.RevokeOAuth2Grant(ctx.FormInt64("id"), ctx.User.ID); err != nil {
|
||||
if err := auth.RevokeOAuth2Grant(ctx.FormInt64("id"), ctx.User.ID); err != nil {
|
||||
ctx.ServerError("RevokeOAuth2Grant", err)
|
||||
return
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package setting
|
||||
package security
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/login"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -29,9 +29,9 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
t, err := login.GetTwoFactorByUID(ctx.User.ID)
|
||||
t, err := auth.GetTwoFactorByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
if login.IsErrTwoFactorNotEnrolled(err) {
|
||||
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = login.UpdateTwoFactor(t); err != nil {
|
||||
if err = auth.UpdateTwoFactor(t); err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err)
|
||||
return
|
||||
}
|
||||
@@ -59,9 +59,9 @@ func DisableTwoFactor(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
t, err := login.GetTwoFactorByUID(ctx.User.ID)
|
||||
t, err := auth.GetTwoFactorByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
if login.IsErrTwoFactorNotEnrolled(err) {
|
||||
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_not_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
}
|
||||
@@ -69,8 +69,8 @@ func DisableTwoFactor(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = login.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
|
||||
if login.IsErrTwoFactorNotEnrolled(err) {
|
||||
if err = auth.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
|
||||
if auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
// There is a potential DB race here - we must have been disabled by another request in the intervening period
|
||||
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
@@ -146,7 +146,7 @@ func EnrollTwoFactor(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
t, err := login.GetTwoFactorByUID(ctx.User.ID)
|
||||
t, err := auth.GetTwoFactorByUID(ctx.User.ID)
|
||||
if t != nil {
|
||||
// already enrolled - we should redirect back!
|
||||
log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User)
|
||||
@@ -154,7 +154,7 @@ func EnrollTwoFactor(ctx *context.Context) {
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
return
|
||||
}
|
||||
if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
|
||||
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err)
|
||||
return
|
||||
}
|
||||
@@ -172,14 +172,14 @@ func EnrollTwoFactorPost(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsSecurity"] = true
|
||||
|
||||
t, err := login.GetTwoFactorByUID(ctx.User.ID)
|
||||
t, err := auth.GetTwoFactorByUID(ctx.User.ID)
|
||||
if t != nil {
|
||||
// already enrolled
|
||||
ctx.Flash.Error(ctx.Tr("settings.twofa_is_enrolled"))
|
||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||
return
|
||||
}
|
||||
if err != nil && !login.IsErrTwoFactorNotEnrolled(err) {
|
||||
if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err)
|
||||
return
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func EnrollTwoFactorPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
t = &login.TwoFactor{
|
||||
t = &auth.TwoFactor{
|
||||
UID: ctx.User.ID,
|
||||
}
|
||||
err = t.SetSecret(secret)
|
||||
@@ -238,7 +238,7 @@ func EnrollTwoFactorPost(ctx *context.Context) {
|
||||
log.Error("Unable to save changes to the session: %v", err)
|
||||
}
|
||||
|
||||
if err = login.NewTwoFactor(t); err != nil {
|
||||
if err = auth.NewTwoFactor(t); err != nil {
|
||||
// FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us.
|
||||
// If there is a unique constraint fail we should just tolerate the error
|
||||
ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err)
|
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package setting
|
||||
package security
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -3,13 +3,13 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package setting
|
||||
package security
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/login"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
tplSettingsSecurity base.TplName = "user/settings/security"
|
||||
tplSettingsTwofaEnroll base.TplName = "user/settings/twofa_enroll"
|
||||
tplSettingsSecurity base.TplName = "user/settings/security/security"
|
||||
tplSettingsTwofaEnroll base.TplName = "user/settings/security/twofa_enroll"
|
||||
)
|
||||
|
||||
// Security render change user's password page and 2FA
|
||||
@@ -56,14 +56,14 @@ func DeleteAccountLink(ctx *context.Context) {
|
||||
}
|
||||
|
||||
func loadSecurityData(ctx *context.Context) {
|
||||
enrolled, err := login.HasTwoFactorByUID(ctx.User.ID)
|
||||
enrolled, err := auth.HasTwoFactorByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("SettingsTwoFactor", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["TOTPEnrolled"] = enrolled
|
||||
|
||||
ctx.Data["U2FRegistrations"], err = login.GetU2FRegistrationsByUID(ctx.User.ID)
|
||||
ctx.Data["U2FRegistrations"], err = auth.GetU2FRegistrationsByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetU2FRegistrationsByUID", err)
|
||||
return
|
||||
@@ -82,10 +82,10 @@ func loadSecurityData(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// map the provider display name with the LoginSource
|
||||
sources := make(map[*login.Source]string)
|
||||
// map the provider display name with the AuthSource
|
||||
sources := make(map[*auth.Source]string)
|
||||
for _, externalAccount := range accountLinks {
|
||||
if loginSource, err := login.GetSourceByID(externalAccount.LoginSourceID); err == nil {
|
||||
if authSource, err := auth.GetSourceByID(externalAccount.LoginSourceID); err == nil {
|
||||
var providerDisplayName string
|
||||
|
||||
type DisplayNamed interface {
|
||||
@@ -96,14 +96,14 @@ func loadSecurityData(ctx *context.Context) {
|
||||
Name() string
|
||||
}
|
||||
|
||||
if displayNamed, ok := loginSource.Cfg.(DisplayNamed); ok {
|
||||
if displayNamed, ok := authSource.Cfg.(DisplayNamed); ok {
|
||||
providerDisplayName = displayNamed.DisplayName()
|
||||
} else if named, ok := loginSource.Cfg.(Named); ok {
|
||||
} else if named, ok := authSource.Cfg.(Named); ok {
|
||||
providerDisplayName = named.Name()
|
||||
} else {
|
||||
providerDisplayName = loginSource.Name
|
||||
providerDisplayName = authSource.Name
|
||||
}
|
||||
sources[loginSource] = providerDisplayName
|
||||
sources[authSource] = providerDisplayName
|
||||
}
|
||||
}
|
||||
ctx.Data["AccountLinks"] = sources
|
@@ -2,13 +2,13 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package setting
|
||||
package security
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/login"
|
||||
"code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@@ -34,7 +34,7 @@ func U2FRegister(ctx *context.Context) {
|
||||
ctx.ServerError("Unable to set session key for u2fChallenge", err)
|
||||
return
|
||||
}
|
||||
regs, err := login.GetU2FRegistrationsByUID(ctx.User.ID)
|
||||
regs, err := auth.GetU2FRegistrationsByUID(ctx.User.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetU2FRegistrationsByUID", err)
|
||||
return
|
||||
@@ -78,7 +78,7 @@ func U2FRegisterPost(ctx *context.Context) {
|
||||
ctx.ServerError("u2f.Register", err)
|
||||
return
|
||||
}
|
||||
if _, err = login.CreateRegistration(ctx.User.ID, name, reg); err != nil {
|
||||
if _, err = auth.CreateRegistration(ctx.User.ID, name, reg); err != nil {
|
||||
ctx.ServerError("u2f.Register", err)
|
||||
return
|
||||
}
|
||||
@@ -88,9 +88,9 @@ func U2FRegisterPost(ctx *context.Context) {
|
||||
// U2FDelete deletes an security key by id
|
||||
func U2FDelete(ctx *context.Context) {
|
||||
form := web.GetForm(ctx).(*forms.U2FDeleteForm)
|
||||
reg, err := login.GetU2FRegistrationByID(form.ID)
|
||||
reg, err := auth.GetU2FRegistrationByID(form.ID)
|
||||
if err != nil {
|
||||
if login.IsErrU2FRegistrationNotExist(err) {
|
||||
if auth.IsErrU2FRegistrationNotExist(err) {
|
||||
ctx.Status(200)
|
||||
return
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func U2FDelete(ctx *context.Context) {
|
||||
ctx.Status(401)
|
||||
return
|
||||
}
|
||||
if err := login.DeleteRegistration(reg); err != nil {
|
||||
if err := auth.DeleteRegistration(reg); err != nil {
|
||||
ctx.ServerError("DeleteRegistration", err)
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user