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