View Javadoc
1   /*
2    * The coLAB project
3    * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
4    *
5    * Licensed under the MIT License
6    */
7   package ch.colabproject.colab.api.security.permissions;
8   
9   import ch.colabproject.colab.api.controller.RequestManager;
10  import ch.colabproject.colab.api.controller.security.SecurityManager;
11  import ch.colabproject.colab.api.model.card.Card;
12  import ch.colabproject.colab.api.model.card.CardContent;
13  import ch.colabproject.colab.api.model.project.Project;
14  import ch.colabproject.colab.api.model.user.User;
15  import java.util.List;
16  import java.util.Objects;
17  import org.apache.commons.lang3.builder.EqualsBuilder;
18  import org.apache.commons.lang3.builder.HashCodeBuilder;
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  
22  /**
23   * Utility class to build conditions
24   *
25   * @author maxence
26   */
27  public final class Conditions {
28  
29      /** logger */
30      private static final Logger logger = LoggerFactory.getLogger(Conditions.class);
31  
32      /**
33       * An always true condition
34       */
35      public static final Condition alwaysTrue = new AlwaysTrue();
36  
37      /**
38       * A always false condition
39       */
40      public static final Condition alwaysFalse = new AlwaysFalse();
41  
42      /**
43       * By default if the situation cannot happen
44       */
45      public static final Condition defaultForOrphan = alwaysTrue;
46  
47      /**
48       * Is the current authenticated condition
49       */
50      public static final Condition authenticated = new IsAuthenticated();
51  
52      /**
53       * Private constructor prevents instantiation
54       */
55      private Conditions() {
56          throw new UnsupportedOperationException("This is a utility class");
57      }
58  
59      /**
60       * Abstract condition. To rule them all
61       */
62      public static abstract class Condition {
63  
64          /**
65           * Evaluate the condition
66           *
67           * @param requestManager  the request Manager
68           * @param securityManager the security manager
69           *
70           * @return evaluation result
71           */
72          public boolean eval(RequestManager requestManager, SecurityManager securityManager) {
73              Boolean cachedResult = requestManager.getConditionResult(this);
74              if (cachedResult != null) {
75                  logger.trace("Condition {} is cached and {}", this, cachedResult);
76                  return cachedResult;
77              } else {
78                  boolean result = this.internalEval(requestManager, securityManager);
79                  logger.trace("Condition {} is not cached and {}", this, result);
80                  requestManager.registerConditionResult(this, result);
81                  return result;
82              }
83          }
84  
85          /**
86           * Evaluate the condition
87           *
88           * @param requestManager  the request Manager
89           * @param securityManager the security manager
90           *
91           * @return evaluation result
92           */
93          protected abstract boolean internalEval(RequestManager requestManager,
94              SecurityManager securityManager);
95      }
96  
97      /**
98       * Always true statement
99       */
100     private static class AlwaysTrue extends Condition {
101 
102         @Override
103         protected boolean internalEval(RequestManager requestManager,
104             SecurityManager securityManager) {
105             return true;
106         }
107 
108         @Override
109         public String toString() {
110             return "true";
111         }
112 
113         @Override
114         public boolean equals(Object obj) {
115             return obj instanceof AlwaysTrue;
116         }
117 
118         @Override
119         public int hashCode() {
120             int hash = 7;
121             hash = 31 * hash + Objects.hashCode(true);
122             return hash;
123         }
124     }
125 
126     /**
127      * Always false statement
128      */
129     private static class AlwaysFalse extends Condition {
130 
131         @Override
132         protected boolean internalEval(RequestManager requestManager,
133             SecurityManager securityManager) {
134             return false;
135         }
136 
137         @Override
138         public String toString() {
139             return "false";
140         }
141 
142         @Override
143         public boolean equals(Object obj) {
144             return obj instanceof AlwaysFalse;
145         }
146 
147         @Override
148         public int hashCode() {
149             int hash = 7;
150             hash = 31 * hash + Objects.hashCode(true);
151             return hash;
152         }
153 
154     }
155 
156     /**
157      * AND condition
158      */
159     public static class And extends Condition {
160 
161         /** Sub conditions */
162         private Condition[] conditions;
163 
164         /**
165          * Build an AND statement
166          *
167          * @param conditions list of all conditions that should be true
168          */
169         public And(Condition... conditions) {
170             this.conditions = conditions;
171         }
172 
173         @Override
174         protected boolean internalEval(RequestManager requestManager,
175             SecurityManager securityManager) {
176             for (Condition c : conditions) {
177                 if (!c.eval(requestManager, securityManager)) {
178                     // not all conditions are true => false
179                     return false;
180                 }
181             }
182             // no falsy condition found => true
183             return true;
184         }
185 
186         @Override
187         public String toString() {
188             return "And(" + List.of(conditions) + ')';
189         }
190 
191         @Override
192         public int hashCode() {
193             int hash = 3;
194             hash = 31 * hash + new HashCodeBuilder().append(this.conditions).toHashCode();
195             return hash;
196         }
197 
198         @Override
199         public boolean equals(Object obj) {
200             if (this == obj) {
201                 return true;
202             }
203             if (obj == null) {
204                 return false;
205             }
206             if (getClass() != obj.getClass()) {
207                 return false;
208             }
209             final And other = (And) obj;
210             if (!Objects.equals(this.conditions, other.conditions)) {
211                 return false;
212             }
213             return true;
214         }
215     }
216 
217     /**
218      * OR condition
219      */
220     public static class Or extends Condition {
221 
222         /** Sub conditions */
223         private Condition[] conditions;
224 
225         /**
226          * Build an OR statement
227          *
228          * @param conditions list of all conditions that should be true
229          */
230         public Or(Condition... conditions) {
231             this.conditions = conditions;
232         }
233 
234         @Override
235         protected boolean internalEval(RequestManager requestManager,
236             SecurityManager securityManager) {
237             for (Condition c : conditions) {
238                 if (c.eval(requestManager, securityManager)) {
239                     // at least on sub condition is true => true
240                     return true;
241                 }
242             }
243             // no true condition found => false
244             return false;
245         }
246 
247         @Override
248         public String toString() {
249             return "Or(" + List.of(conditions) + ')';
250         }
251 
252         @Override
253         public int hashCode() {
254             int hash = 3;
255             hash = 59 * hash + new HashCodeBuilder().append(this.conditions).toHashCode();
256             return hash;
257         }
258 
259         @Override
260         public boolean equals(Object obj) {
261             if (this == obj) {
262                 return true;
263             }
264             if (obj == null) {
265                 return false;
266             }
267             if (getClass() != obj.getClass()) {
268                 return false;
269             }
270             final Or other = (Or) obj;
271             if (!new EqualsBuilder().append(this.conditions, other.conditions).isEquals()) {
272                 return false;
273             } else {
274                 return true;
275             }
276         }
277     }
278 
279     /**
280      * NOT
281      */
282     public static class Not extends Condition {
283 
284         /** the condition to negate */
285         private final Condition condition;
286 
287         /**
288          * Build a NOT statement
289          *
290          * @param condition the condition to negate
291          */
292         public Not(Condition condition) {
293             this.condition = condition;
294         }
295 
296         @Override
297         protected boolean internalEval(RequestManager requestManager,
298             SecurityManager securityManager) {
299             // just invert the given sub-conditions
300             return !condition.eval(requestManager, securityManager);
301         }
302 
303         @Override
304         public String toString() {
305             return "Not(" + condition + ')';
306         }
307 
308         @Override
309         public int hashCode() {
310             int hash = 5;
311             hash = 37 * hash + Objects.hashCode(this.condition);
312             return hash;
313         }
314 
315         @Override
316         public boolean equals(Object obj) {
317             if (this == obj) {
318                 return true;
319             }
320             if (obj == null) {
321                 return false;
322             }
323             if (getClass() != obj.getClass()) {
324                 return false;
325             }
326             final Not other = (Not) obj;
327             if (!Objects.equals(this.condition, other.condition)) {
328                 return false;
329             }
330             return true;
331         }
332     }
333 
334     /**
335      * Is the current user the given one ?
336      */
337     public static class IsCurrentUserThisUser extends Condition {
338 
339         /** user to check against */
340         private final User user;
341 
342         /**
343          * Check who the current user is
344          *
345          * @param user user to check currentUser against
346          */
347         public IsCurrentUserThisUser(User user) {
348             this.user = user;
349         }
350 
351         @Override
352         protected boolean internalEval(RequestManager requestManager,
353             SecurityManager securityManager) {
354             User currentUser = requestManager.getCurrentUser();
355             return currentUser != null && currentUser.equals(user);
356         }
357 
358         @Override
359         public String toString() {
360             return "IsUser(" + user + ")";
361         }
362 
363         @Override
364         public int hashCode() {
365             int hash = 5;
366             hash = 73 * hash + Objects.hashCode(this.user);
367             return hash;
368         }
369 
370         @Override
371         public boolean equals(Object obj) {
372             if (this == obj) {
373                 return true;
374             }
375             if (obj == null) {
376                 return false;
377             }
378             if (getClass() != obj.getClass()) {
379                 return false;
380             }
381             final IsCurrentUserThisUser other = (IsCurrentUserThisUser) obj;
382             if (!Objects.equals(this.user, other.user)) {
383                 return false;
384             }
385             return true;
386         }
387     }
388 
389     /**
390      * The current user must be member of the given project team
391      */
392     public static class IsCurrentUserMemberOfProject extends Condition {
393 
394         /** the project */
395         private final Project project;
396 
397         /**
398          * Create a "Is current user member of this project" statement
399          *
400          * @param project the project to check if the current user is member of
401          */
402         public IsCurrentUserMemberOfProject(Project project) {
403             this.project = project;
404         }
405 
406         @Override
407         protected boolean internalEval(RequestManager requestManager,
408             SecurityManager securityManager) {
409             return securityManager.isCurrentUserMemberOfTheProjectTeam(project);
410         }
411 
412         @Override
413         public String toString() {
414             return "IsMemberOf(" + project + ")";
415         }
416 
417         @Override
418         public int hashCode() {
419             int hash = 7;
420             hash = 37 * hash + Objects.hashCode(this.project);
421             return hash;
422         }
423 
424         @Override
425         public boolean equals(Object obj) {
426             if (this == obj) {
427                 return true;
428             }
429             if (obj == null) {
430                 return false;
431             }
432             if (getClass() != obj.getClass()) {
433                 return false;
434             }
435             final IsCurrentUserMemberOfProject other = (IsCurrentUserMemberOfProject) obj;
436             if (!Objects.equals(this.project, other.project)) {
437                 return false;
438             }
439             return true;
440         }
441     }
442 
443     /**
444      * The current user must be, at least, internal to given project team
445      */
446     public static class IsCurrentUserInternalToProject extends Condition {
447 
448         /** the project */
449         private final Project project;
450 
451         /**
452          * Create a "Is current user internal to this project" statement
453          *
454          * @param project the project to check if the current user is member of
455          */
456         public IsCurrentUserInternalToProject(Project project) {
457             this.project = project;
458         }
459 
460         @Override
461         protected boolean internalEval(RequestManager requestManager,
462             SecurityManager securityManager) {
463             return securityManager.isCurrentUserInternalToProject(project);
464         }
465 
466         @Override
467         public String toString() {
468             return "IsInternalTo(" + project + ")";
469         }
470 
471         @Override
472         public int hashCode() {
473             int hash = 7;
474             hash = 67 * hash + Objects.hashCode(this.project);
475             return hash;
476         }
477 
478         @Override
479         public boolean equals(Object obj) {
480             if (this == obj) {
481                 return true;
482             }
483             if (obj == null) {
484                 return false;
485             }
486             if (getClass() != obj.getClass()) {
487                 return false;
488             }
489             final IsCurrentUserInternalToProject other = (IsCurrentUserInternalToProject) obj;
490             if (!Objects.equals(this.project, other.project)) {
491                 return false;
492             }
493             return true;
494         }
495     }
496 
497     /**
498      * Are current and given users teammate ?
499      */
500     public static class IsCurrentUserTeamMateOfUser extends Condition {
501 
502         /** the other user */
503         private final User user;
504 
505         /**
506          * Create a are teammate statement
507          *
508          * @param user the user to check against
509          */
510         public IsCurrentUserTeamMateOfUser(User user) {
511             this.user = user;
512         }
513 
514         @Override
515         protected boolean internalEval(RequestManager requestManager,
516             SecurityManager securityManager) {
517             User currentUser = requestManager.getCurrentUser();
518             return currentUser != null && securityManager.areUserTeammate(currentUser, this.user);
519         }
520 
521         @Override
522         public String toString() {
523             return "IsTeamMateOf(" + user + ")";
524         }
525 
526         @Override
527         public int hashCode() {
528             int hash = 3;
529             hash = 31 * hash + Objects.hashCode(this.user);
530             return hash;
531         }
532 
533         @Override
534         public boolean equals(Object obj) {
535             if (this == obj) {
536                 return true;
537             }
538             if (obj == null) {
539                 return false;
540             }
541             if (getClass() != obj.getClass()) {
542                 return false;
543             }
544             final IsCurrentUserTeamMateOfUser other = (IsCurrentUserTeamMateOfUser) obj;
545             if (!Objects.equals(this.user, other.user)) {
546                 return false;
547             }
548             return true;
549         }
550     }
551 
552     /**
553      * Do current and given user work on a common project ?
554      */
555     public static class DoCurrentUserWorkOnSameProjectThanUser extends Condition {
556 
557         /** the other user */
558         private final User user;
559 
560         /**
561          * Create a are teammate statement
562          *
563          * @param user the user to check against
564          */
565         public DoCurrentUserWorkOnSameProjectThanUser(User user) {
566             this.user = user;
567         }
568 
569         @Override
570         protected boolean internalEval(RequestManager requestManager,
571             SecurityManager securityManager) {
572             User currentUser = requestManager.getCurrentUser();
573             return currentUser != null
574                 && securityManager.doUsersHaveCommonProject(currentUser, this.user);
575         }
576 
577         @Override
578         public String toString() {
579             return "DoCurrentUserWorkOnSameProjectThanUser(" + user + ")";
580         }
581 
582         @Override
583         public int hashCode() {
584             int hash = 3;
585             hash = 31 * hash + Objects.hashCode(this.user);
586             return hash;
587         }
588 
589         @Override
590         public boolean equals(Object obj) {
591             if (this == obj) {
592                 return true;
593             }
594             if (obj == null) {
595                 return false;
596             }
597             if (getClass() != obj.getClass()) {
598                 return false;
599             }
600             final DoCurrentUserWorkOnSameProjectThanUser other = (DoCurrentUserWorkOnSameProjectThanUser) obj;
601             if (!Objects.equals(this.user, other.user)) {
602                 return false;
603             }
604             return true;
605         }
606     }
607 
608     /**
609      * Is the current user authenticated ?
610      */
611     private static class IsAuthenticated extends Condition {
612 
613         @Override
614         protected boolean internalEval(RequestManager requestManager,
615             SecurityManager securityManager) {
616             return requestManager.isAuthenticated();
617         }
618 
619         @Override
620         public String toString() {
621             return "IsAuthenticated";
622         }
623 
624         @Override
625         public boolean equals(Object obj) {
626             return obj instanceof IsAuthenticated;
627         }
628 
629         @Override
630         public int hashCode() {
631             int hash = 7;
632             hash = 83 * hash;
633             return hash;
634         }
635     }
636 
637     /**
638      * Has the current user write access to a card ?
639      */
640     public static class HasCardWriteRight extends Condition {
641 
642         /** The card * */
643         private final Card card;
644 
645         /**
646          * Create a has write access statement
647          *
648          * @param card the card
649          */
650         public HasCardWriteRight(Card card) {
651             this.card = card;
652         }
653 
654         /**
655          * Create a has write access statement
656          *
657          * @param cardContent a card content
658          */
659         public HasCardWriteRight(CardContent cardContent) {
660             this.card = cardContent.getCard();
661         }
662 
663         @Override
664         protected boolean internalEval(RequestManager requestManager,
665             SecurityManager securityManager) {
666             return securityManager.hasReadWriteAccess(card);
667         }
668 
669         @Override
670         public String toString() {
671             return "HasCardWriteRight(" + card + ")";
672         }
673 
674         @Override
675         public int hashCode() {
676             int hash = 7;
677             hash = 47 * hash + Objects.hashCode(this.card);
678             return hash;
679         }
680 
681         @Override
682         public boolean equals(Object obj) {
683             if (this == obj) {
684                 return true;
685             }
686             if (obj == null) {
687                 return false;
688             }
689             if (getClass() != obj.getClass()) {
690                 return false;
691             }
692             final HasCardWriteRight other = (HasCardWriteRight) obj;
693             if (!Objects.equals(this.card, other.card)) {
694                 return false;
695             }
696             return true;
697         }
698     }
699 
700     /**
701      * Has the current user access to a card ?
702      */
703     public static class HasCardReadRight extends Condition {
704 
705         /** The card * */
706         private final Card card;
707 
708         /**
709          * Create a has read access statement
710          *
711          * @param card the card
712          */
713         public HasCardReadRight(Card card) {
714             this.card = card;
715         }
716 
717         /**
718          * Create a has read access statement
719          *
720          * @param cardContent a card content
721          */
722         public HasCardReadRight(CardContent cardContent) {
723             this.card = cardContent.getCard();
724         }
725 
726         @Override
727         protected boolean internalEval(RequestManager requestManager,
728             SecurityManager securityManager) {
729             return securityManager.hasReadAccess(card);
730         }
731 
732         @Override
733         public String toString() {
734             return "HasCardReadRight(" + card + ")";
735         }
736 
737         @Override
738         public int hashCode() {
739             int hash = 3;
740             hash = 71 * hash + Objects.hashCode(this.card);
741             return hash;
742         }
743 
744         @Override
745         public boolean equals(Object obj) {
746             if (this == obj) {
747                 return true;
748             }
749             if (obj == null) {
750                 return false;
751             }
752             if (getClass() != obj.getClass()) {
753                 return false;
754             }
755             final HasCardReadRight other = (HasCardReadRight) obj;
756             if (!Objects.equals(this.card, other.card)) {
757                 return false;
758             }
759             return true;
760         }
761     }
762 
763 }