eintopf.info@v0.13.16/service/user/authorizer.go (about)

     1  // Copyright (C) 2022 The Eintopf authors
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    15  
    16  package user
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  
    22  	"eintopf.info/internal/crud"
    23  	"eintopf.info/service/auth"
    24  )
    25  
    26  type authorizer struct {
    27  	store Storer
    28  }
    29  
    30  func NewAuthorizer(store Storer) Storer {
    31  	return &authorizer{store}
    32  }
    33  
    34  // Create can only be called
    35  // - if the logged in user role is moderator, admin or internal
    36  // AND
    37  // - if the role of the loggedin user is less than the new role
    38  // Only returns the password field, when the internal role is given.
    39  func (a *authorizer) Create(ctx context.Context, newUser *NewUser) (*User, error) {
    40  	role, err := auth.RoleFromContext(ctx)
    41  	if err != nil {
    42  		return nil, auth.ErrUnauthorized
    43  	}
    44  	if role == auth.RoleNormal {
    45  		return nil, auth.ErrUnauthorized
    46  	}
    47  	err = checkRole(newUser.Role, role)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	user, err := a.store.Create(ctx, newUser)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	if role != auth.RoleInternal {
    56  		user.Password = ""
    57  	}
    58  	return user, nil
    59  }
    60  
    61  // Update can only be called
    62  // - if the loggedin role is internal
    63  // - if the loggedin role is admin
    64  // - if the loggedin user updates itself
    65  // Only returns the password field, when the internal role is given.
    66  func (a *authorizer) Update(ctx context.Context, user *User) (*User, error) {
    67  	role, err := auth.RoleFromContext(ctx)
    68  	if err != nil {
    69  		return nil, auth.ErrUnauthorized
    70  	}
    71  	if role != auth.RoleInternal {
    72  		id, err := auth.UserIDFromContext(ctx)
    73  		if err != nil {
    74  			return nil, auth.ErrUnauthorized
    75  		}
    76  		err = checkRole(user.Role, role)
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  		if role == auth.RoleNormal {
    81  			if id != user.ID {
    82  				// Only the internal and admin role are allowed to modify users
    83  				// other than themself.
    84  				return nil, auth.ErrUnauthorized
    85  			}
    86  		}
    87  		if role == auth.RoleModerator {
    88  			oldUser, err := a.store.FindByID(ctx, user.ID)
    89  			if err != nil {
    90  				return nil, err
    91  			}
    92  			if oldUser == nil {
    93  				return nil, fmt.Errorf("cant find user with id: %s", user.ID)
    94  			}
    95  			switch oldUser.Role {
    96  			case auth.RoleNormal:
    97  				if oldUser.ID != user.ID ||
    98  					oldUser.Email != user.Email ||
    99  					oldUser.Nickname != user.Nickname {
   100  					// Moderators can only change the deactivated and role fields.
   101  					return nil, auth.ErrUnauthorized
   102  				}
   103  			case auth.RoleModerator:
   104  				// A moderator can still update themself.
   105  				if id != user.ID {
   106  					return nil, auth.ErrUnauthorized
   107  				}
   108  			case auth.RoleAdmin:
   109  				return nil, auth.ErrUnauthorized
   110  			}
   111  		}
   112  	}
   113  
   114  	oldUser, err := a.store.FindByID(ctx, user.ID)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	if oldUser == nil {
   119  		return nil, fmt.Errorf("cant find user with id: %s", user.ID)
   120  	}
   121  	if oldUser.Deactivated != user.Deactivated && role == auth.RoleNormal {
   122  		return nil, auth.ErrUnauthorized
   123  	}
   124  
   125  	user, err = a.store.Update(ctx, user)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	if role != auth.RoleInternal {
   130  		user.Password = ""
   131  	}
   132  	return user, nil
   133  }
   134  
   135  // Delete can only be called
   136  // - if the loggedin user is an admin or has the internal role
   137  // - if the loogedin user is deleting itself
   138  func (a *authorizer) Delete(ctx context.Context, id string) error {
   139  	if err := adminOrSelf(ctx, id); err != nil {
   140  		return err
   141  	}
   142  	return a.store.Delete(ctx, id)
   143  }
   144  
   145  // FindByID can only be called
   146  // - if the loggedin user is an admin or has the internal role
   147  // - if the loogedin user is requesting itself
   148  // Only returns the password field, when the internal role is given.
   149  func (a *authorizer) FindByID(ctx context.Context, id string) (*User, error) {
   150  	role, err := auth.RoleFromContext(ctx)
   151  	if err != nil {
   152  		return nil, auth.ErrUnauthorized
   153  	}
   154  	if role != auth.RoleInternal && role != auth.RoleAdmin && role != auth.RoleModerator {
   155  		userID, err := auth.UserIDFromContext(ctx)
   156  		if err != nil {
   157  			return nil, auth.ErrUnauthorized
   158  		}
   159  		if id != userID {
   160  			return nil, auth.ErrUnauthorized
   161  		}
   162  	}
   163  	user, err := a.store.FindByID(ctx, id)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	if user == nil {
   168  		return nil, nil
   169  	}
   170  	if role != auth.RoleInternal {
   171  		user.Password = ""
   172  	}
   173  	return user, nil
   174  }
   175  
   176  // Find can only be called
   177  // - if the loggedin user has the moderator, admin or internal role
   178  // Only the id and nickname field get returned
   179  // - if the user is a normal user
   180  // - if the user is a moderator user for all admin users
   181  // Only returns the password field, when the internal role is given.
   182  func (a *authorizer) Find(ctx context.Context, params *crud.FindParams[FindFilters]) ([]*User, int, error) {
   183  	role, err := auth.RoleFromContext(ctx)
   184  	if err != nil {
   185  		return nil, 0, auth.ErrUnauthorized
   186  	}
   187  	users, total, err := a.store.Find(ctx, params)
   188  	if err != nil {
   189  		return nil, 0, err
   190  	}
   191  	users2 := make([]*User, 0)
   192  
   193  	for _, user := range users {
   194  		switch role {
   195  		case auth.RoleInternal:
   196  			users2 = append(users2, user)
   197  		case auth.RoleAdmin:
   198  			user.Password = ""
   199  			users2 = append(users2, user)
   200  		case auth.RoleModerator:
   201  			user.Password = ""
   202  			users2 = append(users2, user)
   203  		case auth.RoleNormal:
   204  			users2 = append(users2, &User{
   205  				ID: user.ID, Nickname: user.Nickname,
   206  			})
   207  		}
   208  	}
   209  	return users2, total, nil
   210  }
   211  
   212  // checkRole checks that a given role is valid for a given author role.
   213  func checkRole(role auth.Role, author auth.Role) error {
   214  	switch role {
   215  	case auth.RoleInternal:
   216  		// A user with the internal role should never exist.
   217  		return auth.ErrUnauthorized
   218  	case auth.RoleAdmin:
   219  		if author == auth.RoleNormal || author == auth.RoleModerator {
   220  			return auth.ErrUnauthorized
   221  		}
   222  	case auth.RoleModerator:
   223  		if author == auth.RoleNormal {
   224  			return auth.ErrUnauthorized
   225  		}
   226  	}
   227  	return nil
   228  }
   229  
   230  // adminOrSelf checks if the loggedin user
   231  // - has an internal role
   232  // - has an admin role
   233  // - updates itself
   234  // Returns auth.ErrUnauthorized otherwise.
   235  func adminOrSelf(ctx context.Context, userID string) error {
   236  	role, err := auth.RoleFromContext(ctx)
   237  	if err != nil {
   238  		return auth.ErrUnauthorized
   239  	}
   240  	if role == auth.RoleInternal || role == auth.RoleAdmin {
   241  		return nil
   242  	}
   243  	id, err := auth.UserIDFromContext(ctx)
   244  	if err != nil {
   245  		return auth.ErrUnauthorized
   246  	}
   247  	if id == userID {
   248  		return nil
   249  	}
   250  	return auth.ErrUnauthorized
   251  }