eintopf.info@v0.13.16/service/event/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 event
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"strings"
    22  
    23  	"eintopf.info/internal/crud"
    24  	"eintopf.info/service/auth"
    25  )
    26  
    27  // Authorizer wraps a Storer. It checks the authorization before propagating
    28  // the call to the wrapped storer.
    29  type Authorizer struct {
    30  	store           Storer
    31  	groupAuthorizer authorizer
    32  }
    33  
    34  // NewAuthorizer returns a new authorizer limiting access to the provided
    35  // store.
    36  func NewAuthorizer(store Storer, groupAuthorizer authorizer) Storer {
    37  	return &Authorizer{
    38  		store:           store,
    39  		groupAuthorizer: groupAuthorizer,
    40  	}
    41  }
    42  
    43  type authorizer interface {
    44  	IsOwned(ctx context.Context, id string, userID string) bool
    45  }
    46  
    47  // Create can be called by all users.
    48  func (a *Authorizer) Create(ctx context.Context, newEvent *NewEvent) (*Event, error) {
    49  	_, err := auth.UserIDFromContext(ctx)
    50  	if err != nil {
    51  		return nil, auth.ErrUnauthorized
    52  	}
    53  	return a.store.Create(ctx, newEvent)
    54  }
    55  
    56  // Update can be called by an admin user or the event author.
    57  func (a *Authorizer) Update(ctx context.Context, event *Event) (*Event, error) {
    58  	if err := a.adminOrOwned(ctx, event.ID); err != nil {
    59  		return nil, err
    60  	}
    61  	oldEvent, err := a.store.FindByID(ctx, event.ID)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	if oldEvent == nil {
    66  		return nil, fmt.Errorf("cant find event with id: %s", event.ID)
    67  	}
    68  	if oldEvent.Deactivated != event.Deactivated {
    69  		role, err := auth.RoleFromContext(ctx)
    70  		if err != nil {
    71  			return nil, auth.ErrUnauthorized
    72  		}
    73  		if role == auth.RoleNormal {
    74  			return nil, auth.ErrUnauthorized
    75  		}
    76  	}
    77  	return a.store.Update(ctx, event)
    78  }
    79  
    80  // Delete can be called by an admin user or the event author.
    81  func (a *Authorizer) Delete(ctx context.Context, id string) error {
    82  	if err := a.adminOrOwned(ctx, id); err != nil {
    83  		return err
    84  	}
    85  	return a.store.Delete(ctx, id)
    86  }
    87  
    88  // FindByID can be called by everyone.
    89  func (a *Authorizer) FindByID(ctx context.Context, id string) (*Event, error) {
    90  	return a.store.FindByID(ctx, id)
    91  }
    92  
    93  // Find can be called by everyone.
    94  func (a *Authorizer) Find(ctx context.Context, params *crud.FindParams[FindFilters]) ([]*Event, int, error) {
    95  	return a.store.Find(ctx, params)
    96  }
    97  
    98  func (a *Authorizer) adminOrOwned(ctx context.Context, eventID string) error {
    99  	role, err := auth.RoleFromContext(ctx)
   100  	if err != nil {
   101  		return auth.ErrUnauthorized
   102  	}
   103  	if role == auth.RoleAdmin || role == auth.RoleInternal {
   104  		return nil
   105  	}
   106  
   107  	userID, err := auth.UserIDFromContext(ctx)
   108  	if err != nil {
   109  		return auth.ErrUnauthorized
   110  	}
   111  	event, err := a.FindByID(ctx, eventID)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	if a.ownesEvent(ctx, userID, event) {
   116  		return nil
   117  	}
   118  	// Check if the user ownes the parent event.
   119  	if event.Parent != "" {
   120  		parentID := strings.TrimPrefix(event.Parent, "id:")
   121  		parentEvent, err := a.FindByID(ctx, parentID)
   122  		if err != nil {
   123  			return err
   124  		}
   125  
   126  		if a.ownesEvent(ctx, userID, parentEvent) {
   127  			return nil
   128  		}
   129  	}
   130  
   131  	return auth.ErrUnauthorized
   132  }
   133  
   134  func (a *Authorizer) ownesEvent(ctx context.Context, userID string, event *Event) bool {
   135  	if event.IsOwned(userID) {
   136  		return true
   137  	}
   138  	// When an organizer is a Group, check if the user ownes it.
   139  	// They then get permissions for this event.
   140  	for _, organizer := range event.Organizers {
   141  		if strings.Contains(organizer, "id:") {
   142  			groupID := strings.TrimPrefix(organizer, "id:")
   143  			if a.groupAuthorizer.IsOwned(ctx, groupID, userID) {
   144  				return true
   145  			}
   146  		}
   147  	}
   148  	return false
   149  }