forgejo/modules/auth/auth.go

276 lines
6.7 KiB
Go
Raw Normal View History

2014-03-06 11:21:44 +04:00
// Copyright 2014 The Gogs 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 auth
import (
"reflect"
2014-07-26 08:24:27 +04:00
"strings"
2015-08-19 01:22:33 +03:00
"time"
2014-03-06 11:21:44 +04:00
"github.com/Unknwon/com"
2015-10-16 04:28:12 +03:00
"github.com/go-macaron/binding"
"github.com/go-macaron/session"
gouuid "github.com/satori/go.uuid"
2015-10-16 04:28:12 +03:00
"gopkg.in/macaron.v1"
2014-03-06 11:21:44 +04:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2014-03-06 11:21:44 +04:00
)
2016-11-27 09:03:59 +03:00
// IsAPIPath if URL is an api path
func IsAPIPath(url string) bool {
return strings.HasPrefix(url, "/api/")
}
// 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
}
// Check access token.
if IsAPIPath(ctx.Req.URL.Path) {
tokenSHA := ctx.Query("token")
2016-10-07 20:17:27 +03:00
if len(tokenSHA) <= 0 {
tokenSHA = ctx.Query("access_token")
}
if len(tokenSHA) == 0 {
// Well, check with header again.
auHead := ctx.Req.Header.Get("Authorization")
if len(auHead) > 0 {
auths := strings.Fields(auHead)
if len(auths) == 2 && auths[0] == "token" {
tokenSHA = auths[1]
}
}
}
// Let's see if token is valid.
if len(tokenSHA) > 0 {
t, err := models.GetAccessTokenBySHA(tokenSHA)
if err != nil {
2016-06-27 12:02:39 +03:00
if models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err) {
log.Error(4, "GetAccessTokenBySHA: %v", err)
}
return 0
2014-11-12 14:48:50 +03:00
}
t.Updated = time.Now()
2016-01-06 22:41:42 +03:00
if err = models.UpdateAccessToken(t); err != nil {
log.Error(4, "UpdateAccessToken: %v", err)
}
return t.UID
2014-11-12 14:48:50 +03:00
}
}
uid := sess.Get("uid")
if uid == nil {
return 0
} else if id, ok := uid.(int64); ok {
return id
}
2014-07-26 08:24:27 +04:00
return 0
2014-07-03 00:42:16 +04:00
}
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.
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
}
if uid := SignedInID(ctx, sess); uid > 0 {
user, err := models.GetUserByID(uid)
if err == nil {
return user, false
} else if !models.IsErrUserNotExist(err) {
log.Error(4, "GetUserById: %v", err)
}
}
2014-11-10 13:30:07 +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) {
log.Error(4, "GetUserByName: %v", err)
return nil, false
}
2014-12-06 01:54:57 +03:00
// Check if enabled auto-registration.
if setting.Service.EnableReverseProxyAutoRegister {
u := &models.User{
Name: webAuthUser,
Email: gouuid.NewV4().String() + "@localhost",
Passwd: webAuthUser,
IsActive: true,
}
if err = models.CreateUser(u); err != nil {
// FIXME: should I create a system notice?
log.Error(4, "CreateUser: %v", err)
return nil, false
2014-11-10 13:30:07 +03:00
}
return u, false
2014-11-10 13:30:07 +03:00
}
}
return u, false
2014-11-10 13:30:07 +03:00
}
}
2014-11-10 13:30:07 +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" {
uname, passwd, _ := base.BasicAuthDecode(auths[1])
2014-11-10 13:30:07 +03:00
u, err := models.UserSignIn(uname, passwd)
if err != nil {
if !models.IsErrUserNotExist(err) {
log.Error(4, "UserSignIn: %v", err)
}
return nil, false
2014-11-10 13:30:07 +03:00
}
2014-07-26 08:24:27 +04:00
return u, true
}
2014-07-26 08:24:27 +04: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
}
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
} else if len(fieldName) == 0 {
fieldName = com.ToSnakeCase(field.Name)
2014-07-26 08:24:27 +04:00
}
data[fieldName] = val.Field(i).Interface()
}
}
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) {
Oauth2 consumer (#679) * initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
2017-02-22 10:14:37 +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
func GetSize(field reflect.StructField) string {
return getRuleBody(field, "Size(")
}
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 {
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 {
return getRuleBody(field, "MaxSize(")
}
2016-11-27 09:03:59 +03:00
// GetInclude get include in form tag
func GetInclude(field reflect.StructField) string {
return getRuleBody(field, "Include(")
2014-08-01 14:12:14 +04:00
}
2015-08-28 13:33:09 +03:00
// FIXME: struct contains a struct
func validateStruct(obj interface{}) binding.Errors {
return nil
}
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 {
Oauth2 consumer (#679) * initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
2017-02-22 10:14:37 +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")
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")
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
}