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 }