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 }