code.gitea.io/gitea@v1.22.3/models/auth/source.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package auth
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"reflect"
    11  
    12  	"code.gitea.io/gitea/models/db"
    13  	"code.gitea.io/gitea/modules/log"
    14  	"code.gitea.io/gitea/modules/optional"
    15  	"code.gitea.io/gitea/modules/timeutil"
    16  	"code.gitea.io/gitea/modules/util"
    17  
    18  	"xorm.io/builder"
    19  	"xorm.io/xorm"
    20  	"xorm.io/xorm/convert"
    21  )
    22  
    23  // Type represents an login type.
    24  type Type int
    25  
    26  // Note: new type must append to the end of list to maintain compatibility.
    27  const (
    28  	NoType Type = iota
    29  	Plain       // 1
    30  	LDAP        // 2
    31  	SMTP        // 3
    32  	PAM         // 4
    33  	DLDAP       // 5
    34  	OAuth2      // 6
    35  	SSPI        // 7
    36  )
    37  
    38  // String returns the string name of the LoginType
    39  func (typ Type) String() string {
    40  	return Names[typ]
    41  }
    42  
    43  // Int returns the int value of the LoginType
    44  func (typ Type) Int() int {
    45  	return int(typ)
    46  }
    47  
    48  // Names contains the name of LoginType values.
    49  var Names = map[Type]string{
    50  	LDAP:   "LDAP (via BindDN)",
    51  	DLDAP:  "LDAP (simple auth)", // Via direct bind
    52  	SMTP:   "SMTP",
    53  	PAM:    "PAM",
    54  	OAuth2: "OAuth2",
    55  	SSPI:   "SPNEGO with SSPI",
    56  }
    57  
    58  // Config represents login config as far as the db is concerned
    59  type Config interface {
    60  	convert.Conversion
    61  }
    62  
    63  // SkipVerifiable configurations provide a IsSkipVerify to check if SkipVerify is set
    64  type SkipVerifiable interface {
    65  	IsSkipVerify() bool
    66  }
    67  
    68  // HasTLSer configurations provide a HasTLS to check if TLS can be enabled
    69  type HasTLSer interface {
    70  	HasTLS() bool
    71  }
    72  
    73  // UseTLSer configurations provide a HasTLS to check if TLS is enabled
    74  type UseTLSer interface {
    75  	UseTLS() bool
    76  }
    77  
    78  // SSHKeyProvider configurations provide ProvidesSSHKeys to check if they provide SSHKeys
    79  type SSHKeyProvider interface {
    80  	ProvidesSSHKeys() bool
    81  }
    82  
    83  // RegisterableSource configurations provide RegisterSource which needs to be run on creation
    84  type RegisterableSource interface {
    85  	RegisterSource() error
    86  	UnregisterSource() error
    87  }
    88  
    89  var registeredConfigs = map[Type]func() Config{}
    90  
    91  // RegisterTypeConfig register a config for a provided type
    92  func RegisterTypeConfig(typ Type, exemplar Config) {
    93  	if reflect.TypeOf(exemplar).Kind() == reflect.Ptr {
    94  		// Pointer:
    95  		registeredConfigs[typ] = func() Config {
    96  			return reflect.New(reflect.ValueOf(exemplar).Elem().Type()).Interface().(Config)
    97  		}
    98  		return
    99  	}
   100  
   101  	// Not a Pointer
   102  	registeredConfigs[typ] = func() Config {
   103  		return reflect.New(reflect.TypeOf(exemplar)).Elem().Interface().(Config)
   104  	}
   105  }
   106  
   107  // SourceSettable configurations can have their authSource set on them
   108  type SourceSettable interface {
   109  	SetAuthSource(*Source)
   110  }
   111  
   112  // Source represents an external way for authorizing users.
   113  type Source struct {
   114  	ID            int64 `xorm:"pk autoincr"`
   115  	Type          Type
   116  	Name          string             `xorm:"UNIQUE"`
   117  	IsActive      bool               `xorm:"INDEX NOT NULL DEFAULT false"`
   118  	IsSyncEnabled bool               `xorm:"INDEX NOT NULL DEFAULT false"`
   119  	Cfg           convert.Conversion `xorm:"TEXT"`
   120  
   121  	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
   122  	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
   123  }
   124  
   125  // TableName xorm will read the table name from this method
   126  func (Source) TableName() string {
   127  	return "login_source"
   128  }
   129  
   130  func init() {
   131  	db.RegisterModel(new(Source))
   132  }
   133  
   134  // BeforeSet is invoked from XORM before setting the value of a field of this object.
   135  func (source *Source) BeforeSet(colName string, val xorm.Cell) {
   136  	if colName == "type" {
   137  		typ := Type(db.Cell2Int64(val))
   138  		constructor, ok := registeredConfigs[typ]
   139  		if !ok {
   140  			return
   141  		}
   142  		source.Cfg = constructor()
   143  		if settable, ok := source.Cfg.(SourceSettable); ok {
   144  			settable.SetAuthSource(source)
   145  		}
   146  	}
   147  }
   148  
   149  // TypeName return name of this login source type.
   150  func (source *Source) TypeName() string {
   151  	return Names[source.Type]
   152  }
   153  
   154  // IsLDAP returns true of this source is of the LDAP type.
   155  func (source *Source) IsLDAP() bool {
   156  	return source.Type == LDAP
   157  }
   158  
   159  // IsDLDAP returns true of this source is of the DLDAP type.
   160  func (source *Source) IsDLDAP() bool {
   161  	return source.Type == DLDAP
   162  }
   163  
   164  // IsSMTP returns true of this source is of the SMTP type.
   165  func (source *Source) IsSMTP() bool {
   166  	return source.Type == SMTP
   167  }
   168  
   169  // IsPAM returns true of this source is of the PAM type.
   170  func (source *Source) IsPAM() bool {
   171  	return source.Type == PAM
   172  }
   173  
   174  // IsOAuth2 returns true of this source is of the OAuth2 type.
   175  func (source *Source) IsOAuth2() bool {
   176  	return source.Type == OAuth2
   177  }
   178  
   179  // IsSSPI returns true of this source is of the SSPI type.
   180  func (source *Source) IsSSPI() bool {
   181  	return source.Type == SSPI
   182  }
   183  
   184  // HasTLS returns true of this source supports TLS.
   185  func (source *Source) HasTLS() bool {
   186  	hasTLSer, ok := source.Cfg.(HasTLSer)
   187  	return ok && hasTLSer.HasTLS()
   188  }
   189  
   190  // UseTLS returns true of this source is configured to use TLS.
   191  func (source *Source) UseTLS() bool {
   192  	useTLSer, ok := source.Cfg.(UseTLSer)
   193  	return ok && useTLSer.UseTLS()
   194  }
   195  
   196  // SkipVerify returns true if this source is configured to skip SSL
   197  // verification.
   198  func (source *Source) SkipVerify() bool {
   199  	skipVerifiable, ok := source.Cfg.(SkipVerifiable)
   200  	return ok && skipVerifiable.IsSkipVerify()
   201  }
   202  
   203  // CreateSource inserts a AuthSource in the DB if not already
   204  // existing with the given name.
   205  func CreateSource(ctx context.Context, source *Source) error {
   206  	has, err := db.GetEngine(ctx).Where("name=?", source.Name).Exist(new(Source))
   207  	if err != nil {
   208  		return err
   209  	} else if has {
   210  		return ErrSourceAlreadyExist{source.Name}
   211  	}
   212  	// Synchronization is only available with LDAP for now
   213  	if !source.IsLDAP() {
   214  		source.IsSyncEnabled = false
   215  	}
   216  
   217  	_, err = db.GetEngine(ctx).Insert(source)
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	if !source.IsActive {
   223  		return nil
   224  	}
   225  
   226  	if settable, ok := source.Cfg.(SourceSettable); ok {
   227  		settable.SetAuthSource(source)
   228  	}
   229  
   230  	registerableSource, ok := source.Cfg.(RegisterableSource)
   231  	if !ok {
   232  		return nil
   233  	}
   234  
   235  	err = registerableSource.RegisterSource()
   236  	if err != nil {
   237  		// remove the AuthSource in case of errors while registering configuration
   238  		if _, err := db.GetEngine(ctx).ID(source.ID).Delete(new(Source)); err != nil {
   239  			log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
   240  		}
   241  	}
   242  	return err
   243  }
   244  
   245  type FindSourcesOptions struct {
   246  	db.ListOptions
   247  	IsActive  optional.Option[bool]
   248  	LoginType Type
   249  }
   250  
   251  func (opts FindSourcesOptions) ToConds() builder.Cond {
   252  	conds := builder.NewCond()
   253  	if opts.IsActive.Has() {
   254  		conds = conds.And(builder.Eq{"is_active": opts.IsActive.Value()})
   255  	}
   256  	if opts.LoginType != NoType {
   257  		conds = conds.And(builder.Eq{"`type`": opts.LoginType})
   258  	}
   259  	return conds
   260  }
   261  
   262  // IsSSPIEnabled returns true if there is at least one activated login
   263  // source of type LoginSSPI
   264  func IsSSPIEnabled(ctx context.Context) bool {
   265  	exist, err := db.Exist[Source](ctx, FindSourcesOptions{
   266  		IsActive:  optional.Some(true),
   267  		LoginType: SSPI,
   268  	}.ToConds())
   269  	if err != nil {
   270  		log.Error("IsSSPIEnabled: failed to query active SSPI sources: %v", err)
   271  		return false
   272  	}
   273  	return exist
   274  }
   275  
   276  // GetSourceByID returns login source by given ID.
   277  func GetSourceByID(ctx context.Context, id int64) (*Source, error) {
   278  	source := new(Source)
   279  	if id == 0 {
   280  		source.Cfg = registeredConfigs[NoType]()
   281  		// Set this source to active
   282  		// FIXME: allow disabling of db based password authentication in future
   283  		source.IsActive = true
   284  		return source, nil
   285  	}
   286  
   287  	has, err := db.GetEngine(ctx).ID(id).Get(source)
   288  	if err != nil {
   289  		return nil, err
   290  	} else if !has {
   291  		return nil, ErrSourceNotExist{id}
   292  	}
   293  	return source, nil
   294  }
   295  
   296  // UpdateSource updates a Source record in DB.
   297  func UpdateSource(ctx context.Context, source *Source) error {
   298  	var originalSource *Source
   299  	if source.IsOAuth2() {
   300  		// keep track of the original values so we can restore in case of errors while registering OAuth2 providers
   301  		var err error
   302  		if originalSource, err = GetSourceByID(ctx, source.ID); err != nil {
   303  			return err
   304  		}
   305  	}
   306  
   307  	has, err := db.GetEngine(ctx).Where("name=? AND id!=?", source.Name, source.ID).Exist(new(Source))
   308  	if err != nil {
   309  		return err
   310  	} else if has {
   311  		return ErrSourceAlreadyExist{source.Name}
   312  	}
   313  
   314  	_, err = db.GetEngine(ctx).ID(source.ID).AllCols().Update(source)
   315  	if err != nil {
   316  		return err
   317  	}
   318  
   319  	if !source.IsActive {
   320  		return nil
   321  	}
   322  
   323  	if settable, ok := source.Cfg.(SourceSettable); ok {
   324  		settable.SetAuthSource(source)
   325  	}
   326  
   327  	registerableSource, ok := source.Cfg.(RegisterableSource)
   328  	if !ok {
   329  		return nil
   330  	}
   331  
   332  	err = registerableSource.RegisterSource()
   333  	if err != nil {
   334  		// restore original values since we cannot update the provider it self
   335  		if _, err := db.GetEngine(ctx).ID(source.ID).AllCols().Update(originalSource); err != nil {
   336  			log.Error("UpdateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
   337  		}
   338  	}
   339  	return err
   340  }
   341  
   342  // ErrSourceNotExist represents a "SourceNotExist" kind of error.
   343  type ErrSourceNotExist struct {
   344  	ID int64
   345  }
   346  
   347  // IsErrSourceNotExist checks if an error is a ErrSourceNotExist.
   348  func IsErrSourceNotExist(err error) bool {
   349  	_, ok := err.(ErrSourceNotExist)
   350  	return ok
   351  }
   352  
   353  func (err ErrSourceNotExist) Error() string {
   354  	return fmt.Sprintf("login source does not exist [id: %d]", err.ID)
   355  }
   356  
   357  // Unwrap unwraps this as a ErrNotExist err
   358  func (err ErrSourceNotExist) Unwrap() error {
   359  	return util.ErrNotExist
   360  }
   361  
   362  // ErrSourceAlreadyExist represents a "SourceAlreadyExist" kind of error.
   363  type ErrSourceAlreadyExist struct {
   364  	Name string
   365  }
   366  
   367  // IsErrSourceAlreadyExist checks if an error is a ErrSourceAlreadyExist.
   368  func IsErrSourceAlreadyExist(err error) bool {
   369  	_, ok := err.(ErrSourceAlreadyExist)
   370  	return ok
   371  }
   372  
   373  func (err ErrSourceAlreadyExist) Error() string {
   374  	return fmt.Sprintf("login source already exists [name: %s]", err.Name)
   375  }
   376  
   377  // Unwrap unwraps this as a ErrExist err
   378  func (err ErrSourceAlreadyExist) Unwrap() error {
   379  	return util.ErrAlreadyExist
   380  }
   381  
   382  // ErrSourceInUse represents a "SourceInUse" kind of error.
   383  type ErrSourceInUse struct {
   384  	ID int64
   385  }
   386  
   387  // IsErrSourceInUse checks if an error is a ErrSourceInUse.
   388  func IsErrSourceInUse(err error) bool {
   389  	_, ok := err.(ErrSourceInUse)
   390  	return ok
   391  }
   392  
   393  func (err ErrSourceInUse) Error() string {
   394  	return fmt.Sprintf("login source is still used by some users [id: %d]", err.ID)
   395  }