// Copyright 2014 The Gogs Authors. All rights reserved. // 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 auth import ( "reflect" "strings" "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/validation" "gitea.com/macaron/binding" "gitea.com/macaron/macaron" "gitea.com/macaron/session" gouuid "github.com/satori/go.uuid" "github.com/unknwon/com" ) // IsAPIPath if URL is an api path func IsAPIPath(url string) bool { return strings.HasPrefix(url, "/api/") } // 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" } // SignedInID returns the id of signed in user. func SignedInID(ctx *macaron.Context, sess session.Store) int64 { if !models.HasEngine { return 0 } // Check access token. if IsAPIPath(ctx.Req.URL.Path) || IsAttachmentDownload(ctx) { tokenSHA := ctx.Query("token") 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" || strings.ToLower(auths[0]) == "bearer") { tokenSHA = auths[1] } } } // Let's see if token is valid. if len(tokenSHA) > 0 { if strings.Contains(tokenSHA, ".") { uid := CheckOAuthAccessToken(tokenSHA) if uid != 0 { ctx.Data["IsApiToken"] = true } return uid } t, err := models.GetAccessTokenBySHA(tokenSHA) if err != nil { if models.IsErrAccessTokenNotExist(err) || models.IsErrAccessTokenEmpty(err) { log.Error("GetAccessTokenBySHA: %v", err) } return 0 } t.UpdatedUnix = timeutil.TimeStampNow() if err = models.UpdateAccessToken(t); err != nil { log.Error("UpdateAccessToken: %v", err) } ctx.Data["IsApiToken"] = true return t.UID } } uid := sess.Get("uid") if uid == nil { return 0 } else if id, ok := uid.(int64); ok { return id } return 0 } // CheckOAuthAccessToken returns uid of user from oauth token token func CheckOAuthAccessToken(accessToken string) int64 { // JWT tokens require a "." if !strings.Contains(accessToken, ".") { return 0 } token, err := models.ParseOAuth2Token(accessToken) if err != nil { log.Trace("ParseOAuth2Token: %v", err) 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 } // SignedInUser returns the user object of signed user. // 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) { if !models.HasEngine { return nil, false } 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("GetUserById: %v", err) } } 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("GetUserByName: %v", err) return nil, false } // Check if enabled auto-registration. if setting.Service.EnableReverseProxyAutoRegister { email := gouuid.NewV4().String() + "@localhost" if setting.Service.EnableReverseProxyEmail { webAuthEmail := ctx.Req.Header.Get(setting.ReverseProxyAuthEmail) if len(webAuthEmail) > 0 { email = webAuthEmail } } u := &models.User{ Name: webAuthUser, Email: email, Passwd: webAuthUser, IsActive: true, } if err = models.CreateUser(u); err != nil { // FIXME: should I create a system notice? log.Error("CreateUser: %v", err) return nil, false } return u, false } } return u, false } } // 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" { var u *models.User uname, passwd, _ := base.BasicAuthDecode(auths[1]) // 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 } 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 } } token, err := models.GetAccessTokenBySHA(authToken) if err == nil { if isUsernameToken { u, err = models.GetUserByID(token.UID) if err != nil { log.Error("GetUserByID: %v", err) return nil, false } } else { u, err = models.GetUserByName(uname) if err != nil { log.Error("GetUserByID: %v", err) return nil, false } if u.ID != token.UID { return nil, false } } token.UpdatedUnix = timeutil.TimeStampNow() if err = models.UpdateAccessToken(token); err != nil { log.Error("UpdateAccessToken: %v", err) } } else if !models.IsErrAccessTokenNotExist(err) && !models.IsErrAccessTokenEmpty(err) { log.Error("GetAccessTokenBySha: %v", err) } if u == nil { u, err = models.UserSignIn(uname, passwd) if err != nil { if !models.IsErrUserNotExist(err) { log.Error("UserSignIn: %v", err) } return nil, false } } else { ctx.Data["IsApiToken"] = true } return u, true } } return nil, false } // Form form binding interface type Form interface { binding.Validator } func init() { binding.SetNameMapper(com.ToSnakeCase) } // 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) } data[fieldName] = val.Field(i).Interface() } } func getRuleBody(field reflect.StructField, prefix string) string { for _, rule := range strings.Split(field.Tag.Get("binding"), ";") { if strings.HasPrefix(rule, prefix) { return rule[len(prefix) : len(rule)-1] } } return "" } // GetSize get size int form tag func GetSize(field reflect.StructField) string { return getRuleBody(field, "Size(") } // GetMinSize get minimal size in form tag func GetMinSize(field reflect.StructField) string { return getRuleBody(field, "MinSize(") } // GetMaxSize get max size in form tag func GetMaxSize(field reflect.StructField) string { return getRuleBody(field, "MaxSize(") } // GetInclude get include in form tag func GetInclude(field reflect.StructField) string { return getRuleBody(field, "Include(") } func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaron.Locale) binding.Errors { if errs.Len() == 0 { return errs } data["HasError"] = true // If the field with name errs[0].FieldNames[0] is not found in form // somehow, some code later on will panic on Data["ErrorMsg"].(string). // So initialize it to some default. data["ErrorMsg"] = l.Tr("form.unknown_error") AssignForm(f, data) typ := reflect.TypeOf(f) val := reflect.ValueOf(f) if typ.Kind() == reflect.Ptr { typ = typ.Elem() val = val.Elem() } if field, ok := typ.FieldByName(errs[0].FieldNames[0]); ok { fieldName := field.Tag.Get("form") if fieldName != "-" { data["Err_"+field.Name] = true trName := field.Tag.Get("locale") if len(trName) == 0 { trName = l.Tr("form." + field.Name) } else { trName = l.Tr(trName) } switch errs[0].Classification { case binding.ERR_REQUIRED: data["ErrorMsg"] = trName + l.Tr("form.require_error") case binding.ERR_ALPHA_DASH: data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error") case binding.ERR_ALPHA_DASH_DOT: data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error") case validation.ErrGitRefName: data["ErrorMsg"] = trName + l.Tr("form.git_ref_name_error") case binding.ERR_SIZE: data["ErrorMsg"] = trName + l.Tr("form.size_error", GetSize(field)) case binding.ERR_MIN_SIZE: data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field)) case binding.ERR_MAX_SIZE: data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field)) case binding.ERR_EMAIL: data["ErrorMsg"] = trName + l.Tr("form.email_error") case binding.ERR_URL: data["ErrorMsg"] = trName + l.Tr("form.url_error") case binding.ERR_INCLUDE: data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field)) case validation.ErrGlobPattern: data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message) default: data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification } return errs } } return errs }