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