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

     1  // Copyright (C) 2024 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 notification
    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  // NewAuthorizer wraps the given store with authorization methods.
    31  func NewAuthorizer(store Storer) *Authorizer {
    32  	return &Authorizer{store: store}
    33  }
    34  
    35  // Create is allowed by
    36  //   - an admin
    37  //   - a moderator
    38  //   - internally
    39  func (a *Authorizer) Create(ctx context.Context, notification *NewNotification) (*Notification, 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  	return a.store.Create(ctx, notification)
    48  }
    49  
    50  // Update is allowed by
    51  //   - the user owning the notification
    52  //   - an admin
    53  //   - internally
    54  func (a *Authorizer) Update(ctx context.Context, notification *Notification) (*Notification, error) {
    55  	if err := a.adminOrOwned(ctx, notification.ID); err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	return a.store.Update(ctx, notification)
    60  }
    61  
    62  // Delete is allowed by
    63  //   - the user owning the notification
    64  //   - an admin
    65  //   - internally
    66  func (a *Authorizer) Delete(ctx context.Context, id string) error {
    67  	if err := a.adminOrOwned(ctx, id); err != nil {
    68  		return err
    69  	}
    70  
    71  	return a.store.Delete(ctx, id)
    72  }
    73  
    74  // FindByID is allowed by
    75  //   - the user owning the notification
    76  //   - an admin
    77  //   - internally
    78  func (a *Authorizer) FindByID(ctx context.Context, id string) (*Notification, error) {
    79  	if err := a.adminOrOwned(ctx, id); err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	return a.store.FindByID(ctx, id)
    84  }
    85  
    86  // Find is allowed by
    87  //   - normal and moderator for notifications that are owned
    88  //   - an admin
    89  //   - internally
    90  func (a *Authorizer) Find(ctx context.Context, params *crud.FindParams[Filters]) ([]*Notification, int, error) {
    91  	notifications, total, err := a.store.Find(ctx, params)
    92  	if err != nil {
    93  		return nil, 0, err
    94  	}
    95  
    96  	role, err := auth.RoleFromContext(ctx)
    97  	if err != nil {
    98  		return nil, 0, auth.ErrUnauthorized
    99  	}
   100  	if role == auth.RoleAdmin || role == auth.RoleInternal {
   101  		return notifications, total, nil
   102  	}
   103  
   104  	userID, err := auth.UserIDFromContext(ctx)
   105  	if err != nil {
   106  		return nil, 0, auth.ErrUnauthorized
   107  	}
   108  
   109  	filtered := []*Notification{}
   110  	for _, n := range notifications {
   111  		if n.UserID == userID {
   112  			filtered = append(filtered, n)
   113  		} else {
   114  			total--
   115  		}
   116  	}
   117  	return filtered, total, nil
   118  }
   119  
   120  func (a *Authorizer) adminOrOwned(ctx context.Context, id string) error {
   121  	role, err := auth.RoleFromContext(ctx)
   122  	if err != nil {
   123  		return auth.ErrUnauthorized
   124  	}
   125  	if role == auth.RoleNormal || role == auth.RoleModerator {
   126  		userID, err := auth.UserIDFromContext(ctx)
   127  		if err != nil {
   128  			return auth.ErrUnauthorized
   129  		}
   130  
   131  		n, err := a.store.FindByID(auth.ContextWithRole(ctx, auth.RoleInternal), id)
   132  		if err != nil {
   133  			return err
   134  		}
   135  		if n == nil {
   136  			return fmt.Errorf("could not find existing notification")
   137  		}
   138  
   139  		if n.UserID != userID {
   140  			return auth.ErrUnauthorized
   141  		}
   142  	}
   143  	return nil
   144  }
   145  
   146  type SettingsAuthorizer struct {
   147  	store SettingsStorer
   148  }
   149  
   150  // NewSettingsAuthorizer wraps the given store with authorization methods.
   151  func NewSettingsAuthorizer(store SettingsStorer) *SettingsAuthorizer {
   152  	return &SettingsAuthorizer{store: store}
   153  }
   154  
   155  // Create is allowed by
   156  //   - the user owning the settings
   157  //   - an admin
   158  //   - internally
   159  func (a *SettingsAuthorizer) Create(ctx context.Context, settings *NewSettings) (*Settings, error) {
   160  	if err := a.adminOrOwned(ctx, settings.UserID); err != nil {
   161  		return nil, err
   162  	}
   163  	return a.store.Create(ctx, settings)
   164  }
   165  
   166  // Update is allowed by
   167  //   - the user owning the settings
   168  //   - an admin
   169  //   - internally
   170  func (a *SettingsAuthorizer) Update(ctx context.Context, settings *Settings) (*Settings, error) {
   171  	if err := a.adminOrOwned(ctx, settings.UserID); err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	return a.store.Update(ctx, settings)
   176  }
   177  
   178  // Delete is allowed by
   179  //   - the user owning the settings
   180  //   - an admin
   181  //   - internally
   182  func (a *SettingsAuthorizer) Delete(ctx context.Context, id string) error {
   183  	if err := a.adminOrOwned(ctx, id); err != nil {
   184  		return err
   185  	}
   186  
   187  	return a.store.Delete(ctx, id)
   188  }
   189  
   190  // FindByID is allowed by
   191  //   - the user owning the settings
   192  //   - an admin
   193  //   - internally
   194  func (a *SettingsAuthorizer) FindByID(ctx context.Context, id string) (*Settings, error) {
   195  	if err := a.adminOrOwned(ctx, id); err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	return a.store.FindByID(ctx, id)
   200  }
   201  
   202  // Find is allowed by
   203  //   - normal and moderator can only retrieve their own settings
   204  //   - an admin
   205  //   - internally
   206  func (a *SettingsAuthorizer) Find(ctx context.Context, params *crud.FindParams[SettingsFilters]) ([]*Settings, int, error) {
   207  	role, err := auth.RoleFromContext(ctx)
   208  	if err != nil {
   209  		return nil, 0, auth.ErrUnauthorized
   210  	}
   211  	if role == auth.RoleNormal || role == auth.RoleModerator {
   212  		if params == nil {
   213  			params = &crud.FindParams[SettingsFilters]{}
   214  		}
   215  		if params.Filters == nil {
   216  			params.Filters = &SettingsFilters{}
   217  		}
   218  		id, err := auth.UserIDFromContext(ctx)
   219  		if err != nil {
   220  			return nil, 0, auth.ErrUnauthorized
   221  		}
   222  		params.Filters.UserID = &id
   223  	}
   224  
   225  	return a.store.Find(ctx, params)
   226  }
   227  
   228  func (a *SettingsAuthorizer) adminOrOwned(ctx context.Context, id string) error {
   229  	role, err := auth.RoleFromContext(ctx)
   230  	if err != nil {
   231  		return auth.ErrUnauthorized
   232  	}
   233  	if role == auth.RoleNormal || role == auth.RoleModerator {
   234  		userID, err := auth.UserIDFromContext(ctx)
   235  		if err != nil {
   236  			return auth.ErrUnauthorized
   237  		}
   238  
   239  		if id != userID {
   240  			return auth.ErrUnauthorized
   241  		}
   242  	}
   243  	return nil
   244  }