2014-03-06 11:21:44 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-04-26 01:42:50 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2014-03-06 11:21:44 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"reflect"
2014-07-26 08:24:27 +04:00
"strings"
2019-03-08 19:42:50 +03:00
"time"
2014-03-06 11:21:44 +04:00
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2017-04-19 06:02:20 +03:00
"code.gitea.io/gitea/modules/validation"
2019-08-23 19:40:30 +03:00
"gitea.com/macaron/binding"
"gitea.com/macaron/macaron"
"gitea.com/macaron/session"
gouuid "github.com/satori/go.uuid"
"github.com/unknwon/com"
2014-03-06 11:21:44 +04:00
)
2016-11-27 09:03:59 +03:00
// IsAPIPath if URL is an api path
2015-07-15 14:17:57 +03:00
func IsAPIPath ( url string ) bool {
return strings . HasPrefix ( url , "/api/" )
}
2019-08-24 03:33:32 +03:00
// IsAttachmentDownload check if request is a file download (GET) with URL to an attachment
func IsAttachmentDownload ( ctx * macaron . Context ) bool {
return strings . HasPrefix ( ctx . Req . URL . Path , "/attachments/" ) && ctx . Req . Method == "GET"
}
2015-09-02 09:40:15 +03:00
// SignedInID returns the id of signed in user.
func SignedInID ( ctx * macaron . Context , sess session . Store ) int64 {
2014-07-26 08:24:27 +04:00
if ! models . HasEngine {
return 0
}
2015-09-02 09:40:15 +03:00
// Check access token.
2019-08-24 03:33:32 +03:00
if IsAPIPath ( ctx . Req . URL . Path ) || IsAttachmentDownload ( ctx ) {
2015-09-02 09:45:01 +03:00
tokenSHA := ctx . Query ( "token" )
2018-10-21 00:25:14 +03:00
if len ( tokenSHA ) == 0 {
2016-10-07 20:17:27 +03:00
tokenSHA = ctx . Query ( "access_token" )
}
2015-09-02 09:45:01 +03:00
if len ( tokenSHA ) == 0 {
// Well, check with header again.
auHead := ctx . Req . Header . Get ( "Authorization" )
if len ( auHead ) > 0 {
auths := strings . Fields ( auHead )
2019-03-08 19:42:50 +03:00
if len ( auths ) == 2 && ( auths [ 0 ] == "token" || strings . ToLower ( auths [ 0 ] ) == "bearer" ) {
2015-09-02 09:45:01 +03:00
tokenSHA = auths [ 1 ]
}
2015-09-02 09:40:15 +03:00
}
}
2015-09-02 09:45:01 +03:00
// Let's see if token is valid.
if len ( tokenSHA ) > 0 {
2019-03-08 19:42:50 +03:00
if strings . Contains ( tokenSHA , "." ) {
2019-04-26 01:42:50 +03:00
uid := CheckOAuthAccessToken ( tokenSHA )
2019-03-08 19:42:50 +03:00
if uid != 0 {
ctx . Data [ "IsApiToken" ] = true
}
return uid
}
2015-09-02 09:45:01 +03:00
t , err := models . GetAccessTokenBySHA ( tokenSHA )
if err != nil {
2016-06-27 12:02:39 +03:00
if models . IsErrAccessTokenNotExist ( err ) || models . IsErrAccessTokenEmpty ( err ) {
2019-04-02 10:48:31 +03:00
log . Error ( "GetAccessTokenBySHA: %v" , err )
2015-09-02 09:45:01 +03:00
}
return 0
2014-11-12 14:48:50 +03:00
}
2019-08-15 17:46:21 +03:00
t . UpdatedUnix = timeutil . TimeStampNow ( )
2016-01-06 22:41:42 +03:00
if err = models . UpdateAccessToken ( t ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "UpdateAccessToken: %v" , err )
2015-09-02 09:45:01 +03:00
}
2018-09-10 19:15:52 +03:00
ctx . Data [ "IsApiToken" ] = true
2015-09-02 09:45:01 +03:00
return t . UID
2014-11-12 14:48:50 +03:00
}
}
2014-11-13 10:32:18 +03:00
uid := sess . Get ( "uid" )
if uid == nil {
return 0
2017-02-05 16:10:46 +03:00
} else if id , ok := uid . ( int64 ) ; ok {
2014-11-13 10:32:18 +03:00
return id
}
2014-07-26 08:24:27 +04:00
return 0
2014-07-03 00:42:16 +04:00
}
2019-04-26 01:42:50 +03:00
// CheckOAuthAccessToken returns uid of user from oauth token token
func CheckOAuthAccessToken ( accessToken string ) int64 {
2019-03-08 19:42:50 +03:00
// JWT tokens require a "."
if ! strings . Contains ( accessToken , "." ) {
return 0
}
token , err := models . ParseOAuth2Token ( accessToken )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Trace ( "ParseOAuth2Token: %v" , err )
2019-03-08 19:42:50 +03:00
return 0
}
var grant * models . OAuth2Grant
if grant , err = models . GetOAuth2GrantByID ( token . GrantID ) ; err != nil || grant == nil {
return 0
}
if token . Type != models . TypeAccessToken {
return 0
}
if token . ExpiresAt < time . Now ( ) . Unix ( ) || token . IssuedAt > time . Now ( ) . Unix ( ) {
return 0
}
return grant . UserID
}
2014-07-26 08:24:27 +04:00
// SignedInUser returns the user object of signed user.
2014-12-06 01:54:57 +03:00
// It returns a bool value to indicate whether user uses basic auth or not.
2015-09-02 09:40:15 +03:00
func SignedInUser ( ctx * macaron . Context , sess session . Store ) ( * models . User , bool ) {
2014-11-10 13:30:07 +03:00
if ! models . HasEngine {
2014-11-18 19:07:16 +03:00
return nil , false
2014-11-10 13:30:07 +03:00
}
2017-02-05 16:10:46 +03:00
if uid := SignedInID ( ctx , sess ) ; uid > 0 {
user , err := models . GetUserByID ( uid )
if err == nil {
return user , false
} else if ! models . IsErrUserNotExist ( err ) {
2019-04-02 10:48:31 +03:00
log . Error ( "GetUserById: %v" , err )
2017-02-05 16:10:46 +03:00
}
}
2014-11-10 13:30:07 +03:00
2017-02-05 16:10:46 +03:00
if setting . Service . EnableReverseProxyAuth {
webAuthUser := ctx . Req . Header . Get ( setting . ReverseProxyAuthUser )
if len ( webAuthUser ) > 0 {
u , err := models . GetUserByName ( webAuthUser )
if err != nil {
if ! models . IsErrUserNotExist ( err ) {
2019-04-02 10:48:31 +03:00
log . Error ( "GetUserByName: %v" , err )
2017-02-05 16:10:46 +03:00
return nil , false
}
2014-12-06 01:54:57 +03:00
2017-02-05 16:10:46 +03:00
// Check if enabled auto-registration.
if setting . Service . EnableReverseProxyAutoRegister {
2018-12-18 20:05:48 +03:00
email := gouuid . NewV4 ( ) . String ( ) + "@localhost"
if setting . Service . EnableReverseProxyEmail {
webAuthEmail := ctx . Req . Header . Get ( setting . ReverseProxyAuthEmail )
if len ( webAuthEmail ) > 0 {
email = webAuthEmail
}
}
2017-02-05 16:10:46 +03:00
u := & models . User {
Name : webAuthUser ,
2018-12-18 20:05:48 +03:00
Email : email ,
2017-02-05 16:10:46 +03:00
Passwd : webAuthUser ,
IsActive : true ,
}
if err = models . CreateUser ( u ) ; err != nil {
// FIXME: should I create a system notice?
2019-04-02 10:48:31 +03:00
log . Error ( "CreateUser: %v" , err )
2017-02-05 16:10:46 +03:00
return nil , false
2014-11-10 13:30:07 +03:00
}
2017-02-05 16:10:46 +03:00
return u , false
2014-11-10 13:30:07 +03:00
}
}
2017-02-05 16:10:46 +03:00
return u , false
2014-11-10 13:30:07 +03:00
}
2017-02-05 16:10:46 +03:00
}
2014-11-10 13:30:07 +03:00
2017-02-05 16:10:46 +03:00
// Check with basic auth.
baHead := ctx . Req . Header . Get ( "Authorization" )
if len ( baHead ) > 0 {
auths := strings . Fields ( baHead )
if len ( auths ) == 2 && auths [ 0 ] == "Basic" {
2019-02-12 12:20:08 +03:00
var u * models . User
2017-02-05 16:10:46 +03:00
uname , passwd , _ := base . BasicAuthDecode ( auths [ 1 ] )
2014-11-10 13:30:07 +03:00
2019-02-12 12:20:08 +03:00
// Check if username or password is a token
isUsernameToken := len ( passwd ) == 0 || passwd == "x-oauth-basic"
// Assume username is token
authToken := uname
if ! isUsernameToken {
// Assume password is token
authToken = passwd
}
2019-04-26 01:42:50 +03:00
uid := CheckOAuthAccessToken ( authToken )
if uid != 0 {
var err error
ctx . Data [ "IsApiToken" ] = true
u , err = models . GetUserByID ( uid )
if err != nil {
log . Error ( "GetUserByID: %v" , err )
return nil , false
}
}
2019-02-12 12:20:08 +03:00
token , err := models . GetAccessTokenBySHA ( authToken )
if err == nil {
if isUsernameToken {
u , err = models . GetUserByID ( token . UID )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetUserByID: %v" , err )
2019-02-12 12:20:08 +03:00
return nil , false
}
} else {
u , err = models . GetUserByName ( uname )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetUserByID: %v" , err )
2019-02-12 12:20:08 +03:00
return nil , false
}
if u . ID != token . UID {
return nil , false
}
}
2019-08-15 17:46:21 +03:00
token . UpdatedUnix = timeutil . TimeStampNow ( )
2019-02-12 12:20:08 +03:00
if err = models . UpdateAccessToken ( token ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "UpdateAccessToken: %v" , err )
2019-02-12 12:20:08 +03:00
}
2019-06-12 22:41:28 +03:00
} else if ! models . IsErrAccessTokenNotExist ( err ) && ! models . IsErrAccessTokenEmpty ( err ) {
log . Error ( "GetAccessTokenBySha: %v" , err )
2014-11-10 13:30:07 +03:00
}
2019-02-12 12:20:08 +03:00
if u == nil {
u , err = models . UserSignIn ( uname , passwd )
if err != nil {
if ! models . IsErrUserNotExist ( err ) {
2019-04-02 10:48:31 +03:00
log . Error ( "UserSignIn: %v" , err )
2019-02-12 12:20:08 +03:00
}
return nil , false
}
2019-04-19 11:59:26 +03:00
} else {
ctx . Data [ "IsApiToken" ] = true
2019-02-12 12:20:08 +03:00
}
2017-02-05 16:10:46 +03:00
return u , true
}
2014-07-26 08:24:27 +04:00
}
2017-02-05 16:10:46 +03:00
return nil , false
2014-03-29 02:40:31 +04:00
}
2016-11-27 09:03:59 +03:00
// Form form binding interface
2014-10-15 19:19:20 +04:00
type Form interface {
binding . Validator
}
2015-02-01 20:41:03 +03:00
func init ( ) {
binding . SetNameMapper ( com . ToSnakeCase )
}
2014-07-26 08:24:27 +04:00
// AssignForm assign form values back to the template data.
func AssignForm ( form interface { } , data map [ string ] interface { } ) {
typ := reflect . TypeOf ( form )
val := reflect . ValueOf ( form )
if typ . Kind ( ) == reflect . Ptr {
typ = typ . Elem ( )
val = val . Elem ( )
}
for i := 0 ; i < typ . NumField ( ) ; i ++ {
field := typ . Field ( i )
fieldName := field . Tag . Get ( "form" )
// Allow ignored fields in the struct
if fieldName == "-" {
continue
2015-02-01 20:41:03 +03:00
} else if len ( fieldName ) == 0 {
fieldName = com . ToSnakeCase ( field . Name )
2014-07-26 08:24:27 +04:00
}
data [ fieldName ] = val . Field ( i ) . Interface ( )
}
}
2015-10-30 04:09:48 +03:00
func getRuleBody ( field reflect . StructField , prefix string ) string {
2014-07-26 08:24:27 +04:00
for _ , rule := range strings . Split ( field . Tag . Get ( "binding" ) , ";" ) {
2014-08-01 14:12:14 +04:00
if strings . HasPrefix ( rule , prefix ) {
2017-03-03 11:21:31 +03:00
return rule [ len ( prefix ) : len ( rule ) - 1 ]
2014-07-26 08:24:27 +04:00
}
}
return ""
}
2016-11-27 09:03:59 +03:00
// GetSize get size int form tag
2015-07-24 16:02:49 +03:00
func GetSize ( field reflect . StructField ) string {
2015-10-30 04:09:48 +03:00
return getRuleBody ( field , "Size(" )
2015-07-24 16:02:49 +03:00
}
2016-11-27 09:03:59 +03:00
// GetMinSize get minimal size in form tag
2014-08-01 14:12:14 +04:00
func GetMinSize ( field reflect . StructField ) string {
2015-10-30 04:09:48 +03:00
return getRuleBody ( field , "MinSize(" )
2014-08-01 14:12:14 +04:00
}
2016-11-27 09:03:59 +03:00
// GetMaxSize get max size in form tag
2014-08-01 14:12:14 +04:00
func GetMaxSize ( field reflect . StructField ) string {
2015-10-30 04:09:48 +03:00
return getRuleBody ( field , "MaxSize(" )
}
2016-11-27 09:03:59 +03:00
// GetInclude get include in form tag
2015-10-30 04:09:48 +03:00
func GetInclude ( field reflect . StructField ) string {
return getRuleBody ( field , "Include(" )
2014-08-01 14:12:14 +04:00
}
2014-10-15 19:19:20 +04:00
func validate ( errs binding . Errors , data map [ string ] interface { } , f Form , l macaron . Locale ) binding . Errors {
if errs . Len ( ) == 0 {
return errs
2014-07-26 08:24:27 +04:00
}
data [ "HasError" ] = true
AssignForm ( f , data )
typ := reflect . TypeOf ( f )
val := reflect . ValueOf ( f )
if typ . Kind ( ) == reflect . Ptr {
typ = typ . Elem ( )
val = val . Elem ( )
}
for i := 0 ; i < typ . NumField ( ) ; i ++ {
field := typ . Field ( i )
fieldName := field . Tag . Get ( "form" )
// Allow ignored fields in the struct
if fieldName == "-" {
continue
}
2014-10-15 19:19:20 +04:00
if errs [ 0 ] . FieldNames [ 0 ] == field . Name {
2017-03-03 11:21:31 +03:00
data [ "Err_" + field . Name ] = true
2015-07-08 14:47:56 +03:00
trName := field . Tag . Get ( "locale" )
if len ( trName ) == 0 {
trName = l . Tr ( "form." + field . Name )
} else {
trName = l . Tr ( trName )
}
2014-10-15 19:19:20 +04:00
switch errs [ 0 ] . Classification {
2014-12-15 09:49:59 +03:00
case binding . ERR_REQUIRED :
2014-07-26 08:24:27 +04:00
data [ "ErrorMsg" ] = trName + l . Tr ( "form.require_error" )
2014-12-15 09:49:59 +03:00
case binding . ERR_ALPHA_DASH :
2014-07-26 08:24:27 +04:00
data [ "ErrorMsg" ] = trName + l . Tr ( "form.alpha_dash_error" )
2014-12-15 09:49:59 +03:00
case binding . ERR_ALPHA_DASH_DOT :
2014-07-26 08:24:27 +04:00
data [ "ErrorMsg" ] = trName + l . Tr ( "form.alpha_dash_dot_error" )
2017-04-19 06:02:20 +03:00
case validation . ErrGitRefName :
data [ "ErrorMsg" ] = trName + l . Tr ( "form.git_ref_name_error" )
2015-07-24 16:02:49 +03:00
case binding . ERR_SIZE :
data [ "ErrorMsg" ] = trName + l . Tr ( "form.size_error" , GetSize ( field ) )
2014-12-15 09:49:59 +03:00
case binding . ERR_MIN_SIZE :
2014-08-01 14:12:14 +04:00
data [ "ErrorMsg" ] = trName + l . Tr ( "form.min_size_error" , GetMinSize ( field ) )
2014-12-15 09:49:59 +03:00
case binding . ERR_MAX_SIZE :
2014-08-01 14:12:14 +04:00
data [ "ErrorMsg" ] = trName + l . Tr ( "form.max_size_error" , GetMaxSize ( field ) )
2014-12-15 09:49:59 +03:00
case binding . ERR_EMAIL :
2014-07-26 08:24:27 +04:00
data [ "ErrorMsg" ] = trName + l . Tr ( "form.email_error" )
2014-12-15 09:49:59 +03:00
case binding . ERR_URL :
2014-07-26 08:24:27 +04:00
data [ "ErrorMsg" ] = trName + l . Tr ( "form.url_error" )
2015-10-30 04:09:48 +03:00
case binding . ERR_INCLUDE :
data [ "ErrorMsg" ] = trName + l . Tr ( "form.include_error" , GetInclude ( field ) )
2014-07-26 08:24:27 +04:00
default :
2014-10-15 19:19:20 +04:00
data [ "ErrorMsg" ] = l . Tr ( "form.unknown_error" ) + " " + errs [ 0 ] . Classification
2014-07-26 08:24:27 +04:00
}
2014-10-15 19:19:20 +04:00
return errs
2014-07-26 08:24:27 +04:00
}
}
2014-10-15 19:19:20 +04:00
return errs
2014-03-29 02:40:31 +04:00
}