2023-04-25 05:18:42 +02:00
|
|
|
// Copyright Project Harbor Authors
|
2017-04-13 12:54:58 +02:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2016-02-26 11:54:14 +01:00
|
|
|
|
2016-02-25 06:40:08 +01:00
|
|
|
package auth
|
2016-02-01 12:59:10 +01:00
|
|
|
|
|
|
|
import (
|
2021-05-07 10:07:19 +02:00
|
|
|
"context"
|
2018-03-26 07:18:52 +02:00
|
|
|
"errors"
|
2016-02-01 12:59:10 +01:00
|
|
|
"fmt"
|
2021-03-30 07:53:42 +02:00
|
|
|
"time"
|
|
|
|
|
2021-05-07 10:07:19 +02:00
|
|
|
"github.com/goharbor/harbor/src/common"
|
|
|
|
"github.com/goharbor/harbor/src/common/models"
|
2021-04-13 11:36:36 +02:00
|
|
|
"github.com/goharbor/harbor/src/lib/config"
|
2021-04-21 08:11:02 +02:00
|
|
|
libErrors "github.com/goharbor/harbor/src/lib/errors"
|
2021-05-07 10:07:19 +02:00
|
|
|
"github.com/goharbor/harbor/src/lib/log"
|
|
|
|
"github.com/goharbor/harbor/src/pkg/user"
|
2021-03-30 07:53:42 +02:00
|
|
|
"github.com/goharbor/harbor/src/pkg/usergroup/model"
|
2016-02-01 12:59:10 +01:00
|
|
|
)
|
|
|
|
|
2016-10-18 08:39:02 +02:00
|
|
|
// 1.5 seconds
|
|
|
|
const frozenTime time.Duration = 1500 * time.Millisecond
|
|
|
|
|
|
|
|
var lock = NewUserLock(frozenTime)
|
|
|
|
|
2018-04-27 09:45:08 +02:00
|
|
|
// ErrorUserNotExist ...
|
2021-05-12 10:53:37 +02:00
|
|
|
var ErrorUserNotExist = errors.New("user does not exist")
|
2018-04-27 09:45:08 +02:00
|
|
|
|
|
|
|
// ErrorGroupNotExist ...
|
2021-05-12 10:53:37 +02:00
|
|
|
var ErrorGroupNotExist = errors.New("group does not exist")
|
2018-04-27 09:45:08 +02:00
|
|
|
|
2018-08-03 10:05:09 +02:00
|
|
|
// ErrDuplicateLDAPGroup ...
|
2021-05-12 10:53:37 +02:00
|
|
|
var ErrDuplicateLDAPGroup = errors.New("a LDAP user group with same DN already exist")
|
2018-08-03 10:05:09 +02:00
|
|
|
|
|
|
|
// ErrInvalidLDAPGroupDN ...
|
2021-05-12 10:53:37 +02:00
|
|
|
var ErrInvalidLDAPGroupDN = errors.New("the LDAP group DN is invalid")
|
2018-08-03 10:05:09 +02:00
|
|
|
|
2021-04-21 08:11:02 +02:00
|
|
|
// ErrNotSupported ...
|
|
|
|
var ErrNotSupported = errors.New("not supported")
|
|
|
|
|
2018-09-05 10:16:31 +02:00
|
|
|
// ErrAuth is the type of error to indicate a failed authentication due to user's error.
|
2018-03-01 08:40:39 +01:00
|
|
|
type ErrAuth struct {
|
|
|
|
details string
|
|
|
|
}
|
|
|
|
|
2018-09-05 10:16:31 +02:00
|
|
|
// Error ...
|
2018-03-01 08:40:39 +01:00
|
|
|
func (ea ErrAuth) Error() string {
|
|
|
|
return fmt.Sprintf("Failed to authenticate user, due to error '%s'", ea.details)
|
|
|
|
}
|
|
|
|
|
2018-09-05 10:16:31 +02:00
|
|
|
// NewErrAuth ...
|
2018-03-01 08:40:39 +01:00
|
|
|
func NewErrAuth(msg string) ErrAuth {
|
|
|
|
return ErrAuth{details: msg}
|
|
|
|
}
|
|
|
|
|
2017-12-13 13:58:27 +01:00
|
|
|
// AuthenticateHelper provides interface for user management in different auth modes.
|
2017-11-22 05:19:13 +01:00
|
|
|
type AuthenticateHelper interface {
|
2018-03-01 08:40:39 +01:00
|
|
|
// Authenticate authenticate the user based on data in m. Only when the error returned is an instance
|
|
|
|
// of ErrAuth, it will be considered a bad credentials, other errors will be treated as server side error.
|
2021-09-02 13:05:35 +02:00
|
|
|
Authenticate(ctx context.Context, m models.AuthModel) (*models.User, error)
|
2017-11-22 05:19:13 +01:00
|
|
|
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
2017-12-13 13:58:27 +01:00
|
|
|
// put the id in the pointer of user model, if it does exist, fill in the user model based
|
|
|
|
// on the data record of the user
|
2021-09-02 13:05:35 +02:00
|
|
|
OnBoardUser(ctx context.Context, u *models.User) error
|
2021-04-21 08:11:02 +02:00
|
|
|
// OnBoardGroup Create a group in harbor DB, if altGroupName is not empty, take the altGroupName as groupName in harbor DB.
|
2021-09-02 13:05:35 +02:00
|
|
|
OnBoardGroup(ctx context.Context, g *model.UserGroup, altGroupName string) error
|
2021-04-21 08:11:02 +02:00
|
|
|
// SearchUser Get user information from account repository
|
2021-09-02 13:05:35 +02:00
|
|
|
SearchUser(ctx context.Context, username string) (*models.User, error)
|
2021-04-21 08:11:02 +02:00
|
|
|
// SearchGroup Search a group based on specific authentication
|
2021-09-02 13:05:35 +02:00
|
|
|
SearchGroup(ctx context.Context, groupDN string) (*model.UserGroup, error)
|
2021-04-21 08:11:02 +02:00
|
|
|
// PostAuthenticate Update user information after authenticate, such as Onboard or sync info etc
|
2021-09-02 13:05:35 +02:00
|
|
|
PostAuthenticate(ctx context.Context, u *models.User) error
|
2017-12-08 08:07:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultAuthenticateHelper - default AuthenticateHelper implementation
|
|
|
|
type DefaultAuthenticateHelper struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
// Authenticate ...
|
2023-12-19 02:41:26 +01:00
|
|
|
func (d *DefaultAuthenticateHelper) Authenticate(_ context.Context, _ models.AuthModel) (*models.User, error) {
|
2021-04-21 08:11:02 +02:00
|
|
|
return nil, ErrNotSupported
|
2017-12-08 08:07:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
|
|
|
// put the id in the pointer of user model, if it does exist, fill in the user model based
|
|
|
|
// on the data record of the user
|
2023-12-19 02:41:26 +01:00
|
|
|
func (d *DefaultAuthenticateHelper) OnBoardUser(_ context.Context, _ *models.User) error {
|
2021-04-21 08:11:02 +02:00
|
|
|
return ErrNotSupported
|
2017-12-08 08:07:36 +01:00
|
|
|
}
|
|
|
|
|
2018-09-05 10:16:31 +02:00
|
|
|
// SearchUser - Get user information from account repository
|
2023-12-19 02:41:26 +01:00
|
|
|
func (d *DefaultAuthenticateHelper) SearchUser(_ context.Context, username string) (*models.User, error) {
|
2021-04-21 08:11:02 +02:00
|
|
|
log.Errorf("Not support searching user, username: %s", username)
|
|
|
|
return nil, libErrors.NotFoundError(ErrNotSupported).WithMessage("%s not found", username)
|
2017-12-08 08:07:36 +01:00
|
|
|
}
|
|
|
|
|
2018-09-05 10:16:31 +02:00
|
|
|
// PostAuthenticate - Update user information after authenticate, such as OnBoard or sync info etc
|
2023-12-19 02:41:26 +01:00
|
|
|
func (d *DefaultAuthenticateHelper) PostAuthenticate(_ context.Context, _ *models.User) error {
|
2017-12-08 08:07:36 +01:00
|
|
|
return nil
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
|
2018-03-26 07:18:52 +02:00
|
|
|
// OnBoardGroup - OnBoardGroup, it will set the ID of the user group, if altGroupName is not empty, take the altGroupName as groupName in harbor DB.
|
2023-12-19 02:41:26 +01:00
|
|
|
func (d *DefaultAuthenticateHelper) OnBoardGroup(_ context.Context, _ *model.UserGroup, _ string) error {
|
2021-04-21 08:11:02 +02:00
|
|
|
return ErrNotSupported
|
2018-03-26 07:18:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SearchGroup - Search ldap group by group key, groupKey is the unique attribute of group in authenticator, for LDAP, the key is group DN
|
2023-12-19 02:41:26 +01:00
|
|
|
func (d *DefaultAuthenticateHelper) SearchGroup(_ context.Context, groupKey string) (*model.UserGroup, error) {
|
2021-04-21 08:11:02 +02:00
|
|
|
log.Errorf("Not support searching group, group key: %s", groupKey)
|
|
|
|
return nil, libErrors.NotFoundError(ErrNotSupported).WithMessage("%s not found", groupKey)
|
2018-03-26 07:18:52 +02:00
|
|
|
}
|
|
|
|
|
2017-11-22 05:19:13 +01:00
|
|
|
var registry = make(map[string]AuthenticateHelper)
|
2016-02-01 12:59:10 +01:00
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// Register add different authenticators to registry map.
|
2017-12-13 13:58:27 +01:00
|
|
|
func Register(name string, h AuthenticateHelper) {
|
2016-02-01 12:59:10 +01:00
|
|
|
if _, dup := registry[name]; dup {
|
2017-12-21 13:07:23 +01:00
|
|
|
log.Infof("authenticator: %s has been registered,skip", name)
|
2016-02-01 12:59:10 +01:00
|
|
|
return
|
|
|
|
}
|
2017-12-13 13:58:27 +01:00
|
|
|
registry[name] = h
|
2019-01-11 11:16:50 +01:00
|
|
|
log.Debugf("Registered authentication helper for auth mode: %s", name)
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// Login authenticates user credentials based on setting.
|
2021-09-02 13:05:35 +02:00
|
|
|
func Login(ctx context.Context, m models.AuthModel) (*models.User, error) {
|
2021-05-07 10:07:19 +02:00
|
|
|
authMode, err := config.AuthMode(ctx)
|
2016-12-30 11:04:01 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-05-07 10:07:19 +02:00
|
|
|
if authMode == "" || IsSuperUser(ctx, m.Principal) {
|
2018-01-10 08:58:01 +01:00
|
|
|
authMode = common.DBAuth
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
2016-03-25 08:08:32 +01:00
|
|
|
log.Debug("Current AUTH_MODE is ", authMode)
|
2016-02-01 12:59:10 +01:00
|
|
|
|
2016-02-25 06:40:08 +01:00
|
|
|
authenticator, ok := registry[authMode]
|
|
|
|
if !ok {
|
2021-05-12 10:53:37 +02:00
|
|
|
return nil, fmt.Errorf("unrecognized auth_mode: %s", authMode)
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
2016-10-18 08:39:02 +02:00
|
|
|
if lock.IsLocked(m.Principal) {
|
|
|
|
log.Debugf("%s is locked due to login failure, login failed", m.Principal)
|
|
|
|
return nil, nil
|
|
|
|
}
|
2021-09-02 13:05:35 +02:00
|
|
|
user, err := authenticator.Authenticate(ctx, m)
|
2018-03-01 08:40:39 +01:00
|
|
|
if err != nil {
|
|
|
|
if _, ok = err.(ErrAuth); ok {
|
2023-07-18 09:34:28 +02:00
|
|
|
log.Warningf("Login failed, locking %s, and sleep for %v", m.Principal, frozenTime)
|
2018-02-05 14:00:19 +01:00
|
|
|
lock.Lock(m.Principal)
|
|
|
|
time.Sleep(frozenTime)
|
|
|
|
}
|
2018-03-01 08:40:39 +01:00
|
|
|
return nil, err
|
2016-10-18 08:39:02 +02:00
|
|
|
}
|
2021-09-02 13:05:35 +02:00
|
|
|
err = authenticator.PostAuthenticate(ctx, user)
|
2016-10-18 08:39:02 +02:00
|
|
|
return user, err
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
2017-11-22 05:19:13 +01:00
|
|
|
|
2021-09-02 13:05:35 +02:00
|
|
|
func getHelper(ctx context.Context) (AuthenticateHelper, error) {
|
|
|
|
authMode, err := config.AuthMode(ctx)
|
2017-12-13 13:58:27 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-11-22 05:19:13 +01:00
|
|
|
}
|
|
|
|
AuthenticateHelper, ok := registry[authMode]
|
|
|
|
if !ok {
|
2021-05-12 10:53:37 +02:00
|
|
|
return nil, fmt.Errorf("can not get authenticator, authmode: %s", authMode)
|
2017-11-22 05:19:13 +01:00
|
|
|
}
|
|
|
|
return AuthenticateHelper, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OnBoardUser will check if a user exists in user table, if not insert the user and
|
|
|
|
// put the id in the pointer of user model, if it does exist, return the user's profile.
|
2021-09-02 13:05:35 +02:00
|
|
|
func OnBoardUser(ctx context.Context, user *models.User) error {
|
2021-09-27 13:06:28 +02:00
|
|
|
log.Debugf("OnBoardUser, user: %v", user.Username)
|
2021-09-02 13:05:35 +02:00
|
|
|
helper, err := getHelper(ctx)
|
2017-11-22 05:19:13 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-09-02 13:05:35 +02:00
|
|
|
return helper.OnBoardUser(ctx, user)
|
2017-11-22 05:19:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// SearchUser --
|
2021-09-02 13:05:35 +02:00
|
|
|
func SearchUser(ctx context.Context, username string) (*models.User, error) {
|
|
|
|
helper, err := getHelper(ctx)
|
2017-11-22 05:19:13 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-09-02 13:05:35 +02:00
|
|
|
return helper.SearchUser(ctx, username)
|
2017-11-22 05:19:13 +01:00
|
|
|
}
|
2017-12-08 08:07:36 +01:00
|
|
|
|
2018-03-26 07:18:52 +02:00
|
|
|
// OnBoardGroup - Create a user group in harbor db, if altGroupName is not empty, take the altGroupName as groupName in harbor DB
|
2021-09-02 13:05:35 +02:00
|
|
|
func OnBoardGroup(ctx context.Context, userGroup *model.UserGroup, altGroupName string) error {
|
|
|
|
helper, err := getHelper(ctx)
|
2018-03-26 07:18:52 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-09-02 13:05:35 +02:00
|
|
|
return helper.OnBoardGroup(ctx, userGroup, altGroupName)
|
2018-03-26 07:18:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SearchGroup -- Search group in authenticator, groupKey is the unique attribute of group in authenticator, for LDAP, the key is group DN
|
2021-09-02 13:05:35 +02:00
|
|
|
func SearchGroup(ctx context.Context, groupKey string) (*model.UserGroup, error) {
|
|
|
|
helper, err := getHelper(ctx)
|
2018-03-26 07:18:52 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-09-02 13:05:35 +02:00
|
|
|
return helper.SearchGroup(ctx, groupKey)
|
2018-03-26 07:18:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SearchAndOnBoardUser ... Search user and OnBoard user, if user exist, return the ID of current user.
|
2021-09-02 13:05:35 +02:00
|
|
|
func SearchAndOnBoardUser(ctx context.Context, username string) (int, error) {
|
|
|
|
user, err := SearchUser(ctx, username)
|
2020-03-19 07:28:16 +01:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2018-04-27 09:45:08 +02:00
|
|
|
if user == nil {
|
2021-09-02 03:04:33 +02:00
|
|
|
return 0, libErrors.NotFoundError(nil).WithMessage(fmt.Sprintf("user %s is not found", username))
|
2018-04-27 09:45:08 +02:00
|
|
|
}
|
2021-09-02 13:05:35 +02:00
|
|
|
err = OnBoardUser(ctx, user)
|
2018-03-26 07:18:52 +02:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return user.UserID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SearchAndOnBoardGroup ... if altGroupName is not empty, take the altGroupName as groupName in harbor DB
|
2021-09-02 13:05:35 +02:00
|
|
|
func SearchAndOnBoardGroup(ctx context.Context, groupKey, altGroupName string) (int, error) {
|
|
|
|
userGroup, err := SearchGroup(ctx, groupKey)
|
2018-03-26 07:18:52 +02:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2019-09-17 03:52:34 +02:00
|
|
|
if userGroup == nil {
|
|
|
|
return 0, ErrorGroupNotExist
|
|
|
|
}
|
2018-03-26 07:18:52 +02:00
|
|
|
if userGroup != nil {
|
2021-09-02 13:05:35 +02:00
|
|
|
err = OnBoardGroup(ctx, userGroup, altGroupName)
|
2018-03-26 07:18:52 +02:00
|
|
|
}
|
|
|
|
return userGroup.ID, err
|
|
|
|
}
|
|
|
|
|
2017-12-08 08:07:36 +01:00
|
|
|
// PostAuthenticate -
|
2021-09-02 13:05:35 +02:00
|
|
|
func PostAuthenticate(ctx context.Context, u *models.User) error {
|
|
|
|
helper, err := getHelper(ctx)
|
2017-12-08 08:07:36 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-09-02 13:05:35 +02:00
|
|
|
return helper.PostAuthenticate(ctx, u)
|
2017-12-08 08:07:36 +01:00
|
|
|
}
|
2021-05-07 10:07:19 +02:00
|
|
|
|
|
|
|
// IsSuperUser checks if the user is super user(conventionally id == 1) of Harbor
|
|
|
|
func IsSuperUser(ctx context.Context, username string) bool {
|
|
|
|
u, err := user.Mgr.GetByName(ctx, username)
|
|
|
|
if err != nil {
|
2021-09-27 13:06:28 +02:00
|
|
|
// LDAP user can't be found before onboard to Harbor
|
|
|
|
log.Debugf("Failed to get user from DB, username: %s, error: %v", username, err)
|
2021-05-07 10:07:19 +02:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return u.UserID == 1
|
|
|
|
}
|