github.com/greenpau/go-authcrunch@v1.0.50/pkg/identity/user.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package identity
    16  
    17  import (
    18  	"github.com/greenpau/go-authcrunch/pkg/errors"
    19  	"github.com/greenpau/go-authcrunch/pkg/requests"
    20  	"strings"
    21  	"time"
    22  )
    23  
    24  // UserMetadata is metadata associated with a user.
    25  type UserMetadata struct {
    26  	ID           string    `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"`
    27  	Enabled      bool      `json:"enabled,omitempty" xml:"enabled,omitempty" yaml:"enabled,omitempty"`
    28  	Username     string    `json:"username,omitempty" xml:"username,omitempty" yaml:"username,omitempty"`
    29  	Title        string    `json:"title,omitempty" xml:"title,omitempty" yaml:"title,omitempty"`
    30  	Name         string    `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
    31  	Email        string    `json:"email,omitempty" xml:"email,omitempty" yaml:"email,omitempty"`
    32  	Created      time.Time `json:"created,omitempty" xml:"created,omitempty" yaml:"created,omitempty"`
    33  	LastModified time.Time `json:"last_modified,omitempty" xml:"last_modified,omitempty" yaml:"last_modified,omitempty"`
    34  	Revision     int       `json:"revision,omitempty" xml:"revision,omitempty" yaml:"revision,omitempty"`
    35  	Avatar       string    `json:"avatar,omitempty" xml:"avatar,omitempty" yaml:"avatar,omitempty"`
    36  }
    37  
    38  // UserMetadataBundle is a collection of public users.
    39  type UserMetadataBundle struct {
    40  	users []*UserMetadata
    41  	size  int
    42  }
    43  
    44  // User is a user identity.
    45  type User struct {
    46  	ID             string          `json:"id,omitempty" xml:"id,omitempty" yaml:"id,omitempty"`
    47  	Enabled        bool            `json:"enabled,omitempty" xml:"enabled,omitempty" yaml:"enabled,omitempty"`
    48  	Human          bool            `json:"human,omitempty" xml:"human,omitempty" yaml:"human,omitempty"`
    49  	Username       string          `json:"username,omitempty" xml:"username,omitempty" yaml:"username,omitempty"`
    50  	Title          string          `json:"title,omitempty" xml:"title,omitempty" yaml:"title,omitempty"`
    51  	Name           *Name           `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
    52  	Organization   *Organization   `json:"organization,omitempty" xml:"organization,omitempty" yaml:"organization,omitempty"`
    53  	Names          []*Name         `json:"names,omitempty" xml:"names,omitempty" yaml:"names,omitempty"`
    54  	Organizations  []*Organization `json:"organizations,omitempty" xml:"organizations,omitempty" yaml:"organizations,omitempty"`
    55  	StreetAddress  []*Location     `json:"street_address,omitempty" xml:"street_address,omitempty" yaml:"street_address,omitempty"`
    56  	EmailAddress   *EmailAddress   `json:"email_address,omitempty" xml:"email_address,omitempty" yaml:"email_address,omitempty"`
    57  	EmailAddresses []*EmailAddress `json:"email_addresses,omitempty" xml:"email_addresses,omitempty" yaml:"email_addresses,omitempty"`
    58  	Passwords      []*Password     `json:"passwords,omitempty" xml:"passwords,omitempty" yaml:"passwords,omitempty"`
    59  	PublicKeys     []*PublicKey    `json:"public_keys,omitempty" xml:"public_keys,omitempty" yaml:"public_keys,omitempty"`
    60  	APIKeys        []*APIKey       `json:"api_keys,omitempty" xml:"api_keys,omitempty" yaml:"api_keys,omitempty"`
    61  	MfaTokens      []*MfaToken     `json:"mfa_tokens,omitempty" xml:"mfa_tokens,omitempty" yaml:"mfa_tokens,omitempty"`
    62  	Lockout        *LockoutState   `json:"lockout,omitempty" xml:"lockout,omitempty" yaml:"lockout,omitempty"`
    63  	Avatar         *Image          `json:"avatar,omitempty" xml:"avatar,omitempty" yaml:"avatar,omitempty"`
    64  	Created        time.Time       `json:"created,omitempty" xml:"created,omitempty" yaml:"created,omitempty"`
    65  	LastModified   time.Time       `json:"last_modified,omitempty" xml:"last_modified,omitempty" yaml:"last_modified,omitempty"`
    66  	Revision       int             `json:"revision,omitempty" xml:"revision,omitempty" yaml:"revision,omitempty"`
    67  	Roles          []*Role         `json:"roles,omitempty" xml:"roles,omitempty" yaml:"roles,omitempty"`
    68  	Registration   *Registration   `json:"registration,omitempty" xml:"registration,omitempty" yaml:"registration,omitempty"`
    69  	rolesRef       map[string]interface{}
    70  }
    71  
    72  // NewUserMetadataBundle returns an instance of UserMetadataBundle.
    73  func NewUserMetadataBundle() *UserMetadataBundle {
    74  	return &UserMetadataBundle{
    75  		users: []*UserMetadata{},
    76  	}
    77  }
    78  
    79  // Add adds UserMetadata to UserMetadataBundle.
    80  func (b *UserMetadataBundle) Add(k *UserMetadata) {
    81  	b.users = append(b.users, k)
    82  	b.size++
    83  }
    84  
    85  // Get returns UserMetadata instances of the UserMetadataBundle.
    86  func (b *UserMetadataBundle) Get() []*UserMetadata {
    87  	return b.users
    88  }
    89  
    90  // Size returns the number of UserMetadata instances in UserMetadataBundle.
    91  func (b *UserMetadataBundle) Size() int {
    92  	return b.size
    93  }
    94  
    95  // NewUser returns an instance of User.
    96  func NewUser(s string) *User {
    97  	user := &User{
    98  		ID:           NewID(),
    99  		Username:     s,
   100  		Created:      time.Now().UTC(),
   101  		LastModified: time.Now().UTC(),
   102  	}
   103  	return user
   104  }
   105  
   106  // NewUserWithRoles returns User with additional fields.
   107  func NewUserWithRoles(username, password, email, fullName string, roles []string) (*User, error) {
   108  	user := NewUser(username)
   109  	if err := user.AddPassword(password, 0); err != nil {
   110  		return nil, err
   111  	}
   112  	if err := user.AddEmailAddress(email); err != nil {
   113  		return nil, err
   114  	}
   115  	if err := user.AddRoles(roles); err != nil {
   116  		return nil, err
   117  	}
   118  	fullName = strings.TrimSpace(fullName)
   119  	if fullName != "" {
   120  		name, err := ParseName(fullName)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		err = user.AddName(name)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  	}
   129  	if err := user.Valid(); err != nil {
   130  		return nil, err
   131  	}
   132  	user.Revision = 0
   133  	return user, nil
   134  }
   135  
   136  // Valid returns true if a user conforms to a standard.
   137  func (user *User) Valid() error {
   138  	if len(user.ID) != 36 {
   139  		return errors.ErrUserIDInvalidLength.WithArgs(len(user.ID))
   140  	}
   141  	if user.Username == "" {
   142  		return errors.ErrUsernameEmpty
   143  	}
   144  	if len(user.Passwords) < 1 {
   145  		return errors.ErrUserPasswordNotFound
   146  	}
   147  	return nil
   148  }
   149  
   150  // AddPassword returns creates and adds password for a user identity.
   151  func (user *User) AddPassword(s string, keepVersions int) error {
   152  	var passwords []*Password
   153  	password, err := NewPassword(s)
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	// Check if the existing password is the same as the one provided.
   159  	if len(user.Passwords) > 0 {
   160  		if user.Passwords[0].Hash == password.Hash {
   161  			return nil
   162  		}
   163  	}
   164  
   165  	if keepVersions < 1 {
   166  		keepVersions = 9
   167  	}
   168  	passwords = append(passwords, password)
   169  	if len(user.Passwords) > 0 {
   170  		for i, p := range user.Passwords {
   171  			if !p.Disabled {
   172  				p.Disable()
   173  			}
   174  			passwords = append(passwords, p)
   175  			if i > keepVersions {
   176  				break
   177  			}
   178  		}
   179  	}
   180  	user.Passwords = passwords
   181  	user.Revise()
   182  	return nil
   183  }
   184  
   185  // AddEmailAddress returns creates and adds password for a user identity.
   186  func (user *User) AddEmailAddress(s string) error {
   187  	email, err := NewEmailAddress(s)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	if len(user.EmailAddresses) == 0 {
   192  		user.EmailAddress = email
   193  		user.EmailAddresses = append(user.EmailAddresses, email)
   194  		user.Revise()
   195  		return nil
   196  	}
   197  	for _, e := range user.EmailAddresses {
   198  		if email.Address == e.Address {
   199  			return nil
   200  		}
   201  	}
   202  	user.EmailAddresses = append(user.EmailAddresses, email)
   203  	user.Revise()
   204  	return nil
   205  }
   206  
   207  // HasEmailAddresses checks whether a user has email address.
   208  func (user *User) HasEmailAddresses() bool {
   209  	if len(user.EmailAddresses) == 0 {
   210  		return false
   211  	}
   212  	return true
   213  }
   214  
   215  // HasRoles checks whether a user has a role.
   216  func (user *User) HasRoles() bool {
   217  	if len(user.Roles) == 0 {
   218  		return false
   219  	}
   220  	return true
   221  }
   222  
   223  // HasRole checks whether a user has a specific role.
   224  func (user *User) HasRole(s string) bool {
   225  	if len(user.Roles) == 0 {
   226  		return false
   227  	}
   228  	role, err := NewRole(s)
   229  	if err != nil {
   230  		return false
   231  	}
   232  
   233  	for _, r := range user.Roles {
   234  		if (r.Name == role.Name) && (r.Organization == role.Organization) {
   235  			return true
   236  		}
   237  	}
   238  	return false
   239  }
   240  
   241  // AddRoles adds roles to a user identity.
   242  func (user *User) AddRoles(roles []string) error {
   243  	for _, role := range roles {
   244  		if err := user.AddRole(role); err != nil {
   245  			return err
   246  		}
   247  	}
   248  	return nil
   249  }
   250  
   251  // AddRole adds a role to a user identity.
   252  func (user *User) AddRole(s string) error {
   253  	role, err := NewRole(s)
   254  	if err != nil {
   255  		return err
   256  	}
   257  	if len(user.Roles) == 0 {
   258  		user.Roles = append(user.Roles, role)
   259  		user.Revise()
   260  		return nil
   261  	}
   262  	for _, r := range user.Roles {
   263  		if (r.Name == role.Name) && (r.Organization == role.Organization) {
   264  			return nil
   265  		}
   266  	}
   267  	user.Roles = append(user.Roles, role)
   268  	user.Revise()
   269  	return nil
   270  }
   271  
   272  // VerifyPassword verifies provided password matches to the one in the database.
   273  func (user *User) VerifyPassword(s string) error {
   274  	if len(user.Passwords) == 0 {
   275  		return errors.ErrUserPasswordNotFound
   276  	}
   277  	for _, p := range user.Passwords {
   278  		if p.Disabled || p.Expired {
   279  			continue
   280  		}
   281  		if p.Match(s) {
   282  			return nil
   283  		}
   284  	}
   285  	return errors.ErrUserPasswordInvalid
   286  }
   287  
   288  // VerifyWebAuthnRequest authenticated WebAuthn requests.
   289  func (user *User) VerifyWebAuthnRequest(r *requests.Request) error {
   290  	req, err := unpackWebAuthnRequest(r.WebAuthn.Request)
   291  	if err != nil {
   292  		return err
   293  	}
   294  	for _, token := range user.MfaTokens {
   295  		if token.Disabled {
   296  			continue
   297  		}
   298  		if token.Type != "u2f" {
   299  			continue
   300  		}
   301  		if _, exists := token.Parameters["u2f_id"]; !exists {
   302  			continue
   303  		}
   304  		if req.ID != token.Parameters["u2f_id"] {
   305  			continue
   306  		}
   307  		resp, err := token.WebAuthnRequest(r.WebAuthn.Request)
   308  		if err != nil {
   309  			return errors.ErrWebAuthnVerifyRequest
   310  		}
   311  		if resp == nil {
   312  			return errors.ErrWebAuthnVerifyRequest
   313  		}
   314  		if resp.ClientData.Challenge != r.WebAuthn.Challenge {
   315  			return errors.ErrWebAuthnVerifyRequest
   316  		}
   317  		return nil
   318  	}
   319  	return errors.ErrWebAuthnVerifyRequest
   320  }
   321  
   322  // GetMailClaim returns primary email address.
   323  func (user *User) GetMailClaim() string {
   324  	if len(user.EmailAddresses) == 0 {
   325  		return ""
   326  	}
   327  	for _, mail := range user.EmailAddresses {
   328  		if mail.Primary() {
   329  			return mail.Address
   330  		}
   331  	}
   332  	return user.EmailAddresses[0].Address
   333  }
   334  
   335  // GetNameClaim returns name field of a claim.
   336  func (user *User) GetNameClaim() string {
   337  	if user.Name == nil {
   338  		return ""
   339  	}
   340  	if name := user.Name.GetNameClaim(); name != "" {
   341  		return name
   342  	}
   343  	return ""
   344  }
   345  
   346  // GetRolesClaim returns name field of a claim.
   347  func (user *User) GetRolesClaim() []string {
   348  	var roles []string
   349  	if len(user.Roles) == 0 {
   350  		return roles
   351  	}
   352  	for _, role := range user.Roles {
   353  		roles = append(roles, role.String())
   354  	}
   355  	return roles
   356  }
   357  
   358  // GetFullName returns the primary full name for a user.
   359  func (user *User) GetFullName() string {
   360  	if user.Name == nil {
   361  		return ""
   362  	}
   363  	return user.Name.GetFullName()
   364  }
   365  
   366  // AddName adds Name for a user identity.
   367  func (user *User) AddName(name *Name) error {
   368  	if len(user.Names) == 0 {
   369  		user.Name = name
   370  		user.Names = append(user.Names, name)
   371  		return nil
   372  	}
   373  	for _, n := range user.Names {
   374  		if name.GetFullName() == n.GetFullName() {
   375  			return nil
   376  		}
   377  	}
   378  	user.Names = append(user.Names, name)
   379  	user.Revise()
   380  	return nil
   381  }
   382  
   383  // AddPublicKey adds public key, e.g. GPG or SSH, to a user identity.
   384  func (user *User) AddPublicKey(r *requests.Request) error {
   385  	key, err := NewPublicKey(r)
   386  	if err != nil {
   387  		return errors.ErrAddPublicKey.WithArgs(r.Key.Usage, err)
   388  	}
   389  	for _, k := range user.PublicKeys {
   390  		if k.Type != key.Type {
   391  			continue
   392  		}
   393  		if k.Fingerprint != key.Fingerprint {
   394  			continue
   395  		}
   396  		return errors.ErrAddPublicKey.WithArgs(r.Key.Usage, "already exists")
   397  	}
   398  	user.PublicKeys = append(user.PublicKeys, key)
   399  	user.Revise()
   400  	return nil
   401  }
   402  
   403  // DeletePublicKey deletes a public key associated with a user.
   404  func (user *User) DeletePublicKey(r *requests.Request) error {
   405  	var found bool
   406  	keys := []*PublicKey{}
   407  	for _, k := range user.PublicKeys {
   408  		if k.ID == r.Key.ID {
   409  			found = true
   410  			continue
   411  		}
   412  		keys = append(keys, k)
   413  	}
   414  	if !found {
   415  		return errors.ErrDeletePublicKey.WithArgs(r.Key.ID, "not found")
   416  	}
   417  	user.PublicKeys = keys
   418  	user.Revise()
   419  	return nil
   420  }
   421  
   422  // AddAPIKey adds API key to a user identity.
   423  func (user *User) AddAPIKey(r *requests.Request) error {
   424  	key, err := NewAPIKey(r)
   425  	if err != nil {
   426  		return errors.ErrAddAPIKey.WithArgs(r.Key.Usage, err)
   427  	}
   428  	user.APIKeys = append(user.APIKeys, key)
   429  	user.Revise()
   430  	return nil
   431  }
   432  
   433  // DeleteAPIKey deletes an API key associated with a user.
   434  func (user *User) DeleteAPIKey(r *requests.Request) error {
   435  	var found bool
   436  	keys := []*APIKey{}
   437  	for _, k := range user.APIKeys {
   438  		if k.ID == r.Key.ID {
   439  			found = true
   440  			r.Key.Prefix = k.Prefix
   441  			continue
   442  		}
   443  		keys = append(keys, k)
   444  	}
   445  	if !found {
   446  		return errors.ErrDeleteAPIKey.WithArgs(r.Key.ID, "not found")
   447  	}
   448  	user.APIKeys = keys
   449  	user.Revise()
   450  	return nil
   451  }
   452  
   453  // LookupAPIKey performs the lookup of API key.
   454  func (user *User) LookupAPIKey(r *requests.Request) error {
   455  	for _, k := range user.APIKeys {
   456  		if k.Prefix == r.Key.Prefix {
   457  			if k.Match(r.Key.Payload) {
   458  				return nil
   459  			}
   460  			return errors.ErrLookupAPIKeyFailed
   461  		}
   462  	}
   463  	return errors.ErrLookupAPIKeyFailed
   464  }
   465  
   466  // AddMfaToken adds MFA token to a user identity.
   467  func (user *User) AddMfaToken(r *requests.Request) error {
   468  	token, err := NewMfaToken(r)
   469  	if err != nil {
   470  		return errors.ErrAddMfaToken.WithArgs(err)
   471  	}
   472  	for _, k := range user.MfaTokens {
   473  		if k.Secret == token.Secret {
   474  			return errors.ErrAddMfaToken.WithArgs(errors.ErrDuplicateMfaTokenSecret)
   475  		}
   476  		if k.Comment == token.Comment {
   477  			return errors.ErrAddMfaToken.WithArgs(errors.ErrDuplicateMfaTokenComment)
   478  		}
   479  	}
   480  	user.MfaTokens = append(user.MfaTokens, token)
   481  	user.Revise()
   482  	return nil
   483  }
   484  
   485  // DeleteMfaToken deletes MFA token associated with a user.
   486  func (user *User) DeleteMfaToken(r *requests.Request) error {
   487  	var found bool
   488  	tokens := []*MfaToken{}
   489  	for _, k := range user.MfaTokens {
   490  		if k.ID == r.MfaToken.ID {
   491  			found = true
   492  			continue
   493  		}
   494  		tokens = append(tokens, k)
   495  	}
   496  	if !found {
   497  		return errors.ErrDeleteMfaToken.WithArgs(r.MfaToken.ID, "not found")
   498  	}
   499  	user.MfaTokens = tokens
   500  	user.Revise()
   501  	return nil
   502  }
   503  
   504  // GetFlags populates request context with metadata about a user.
   505  func (user *User) GetFlags(r *requests.Request) {
   506  	for _, token := range user.MfaTokens {
   507  		if token.Disabled {
   508  			continue
   509  		}
   510  		r.Flags.MfaConfigured = true
   511  		switch token.Type {
   512  		case "totp":
   513  			r.Flags.MfaApp = true
   514  		case "u2f":
   515  			r.Flags.MfaUniversal = true
   516  		}
   517  	}
   518  }
   519  
   520  // ChangePassword changes user password.
   521  func (user *User) ChangePassword(r *requests.Request, keepVersions int) error {
   522  	if err := user.VerifyPassword(r.User.OldPassword); err != nil {
   523  		return errors.ErrChangeUserPassword.WithArgs(err)
   524  	}
   525  	if err := user.AddPassword(r.User.Password, keepVersions); err != nil {
   526  		return errors.ErrChangeUserPassword.WithArgs(err)
   527  	}
   528  	return nil
   529  }
   530  
   531  // UpdatePassword update user password.
   532  func (user *User) UpdatePassword(r *requests.Request, keepVersions int) error {
   533  	if !strings.HasPrefix(r.User.Password, "bcrypt:") {
   534  		// Check whether the existing password matches the newly provided password,
   535  		// and skip updating if it is.
   536  		if user.VerifyPassword(r.User.Password) == nil {
   537  			return nil
   538  		}
   539  	}
   540  	if err := user.AddPassword(r.User.Password, keepVersions); err != nil {
   541  		return errors.ErrUpdateUserPassword.WithArgs(err)
   542  	}
   543  	return nil
   544  }
   545  
   546  // GetMetadata returns user metadata.
   547  func (user *User) GetMetadata() *UserMetadata {
   548  	m := &UserMetadata{
   549  		ID:           user.ID,
   550  		Enabled:      user.Enabled,
   551  		Username:     user.Username,
   552  		Title:        user.Title,
   553  		Created:      user.Created,
   554  		LastModified: user.LastModified,
   555  		Revision:     user.Revision,
   556  	}
   557  	if user.Avatar != nil {
   558  		m.Avatar = user.Avatar.Path
   559  	}
   560  	if user.EmailAddress != nil {
   561  		m.Email = user.EmailAddress.ToString()
   562  	}
   563  	if user.Name != nil {
   564  		m.Name = user.Name.ToString()
   565  	}
   566  	return m
   567  }
   568  
   569  // GetChallenges returns a list of challenges that should be
   570  // satisfied prior to successfully authenticating a user.
   571  func (user *User) GetChallenges() []string {
   572  	var challenges []string
   573  	challenges = append(challenges, "password")
   574  	if len(user.MfaTokens) > 0 {
   575  		challenges = append(challenges, "mfa")
   576  	}
   577  	return challenges
   578  }
   579  
   580  // Revise increments revision number and last modified timestamp.
   581  func (user *User) Revise() {
   582  	user.Revision++
   583  	user.LastModified = time.Now().UTC()
   584  }
   585  
   586  // HasAdminRights returns true if the user has admin rights.
   587  func (user *User) HasAdminRights() bool {
   588  	for _, role := range user.Roles {
   589  		if role.Name == "admin" && role.Organization == "authp" {
   590  			return true
   591  		}
   592  	}
   593  	return false
   594  }