Merge "Move adsal into its own subdirectory."
[controller.git] / opendaylight / adsal / appauth / src / main / java / org / opendaylight / controller / appauth / authorization / Authorization.java
1
2 /*
3  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9
10 package org.opendaylight.controller.appauth.authorization;
11
12 import java.util.ArrayList;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Map.Entry;
16 import java.util.Set;
17 import java.util.concurrent.ConcurrentMap;
18
19 import org.opendaylight.controller.configuration.ConfigurationObject;
20 import org.opendaylight.controller.containermanager.IContainerAuthorization;
21 import org.opendaylight.controller.sal.authorization.AppRoleLevel;
22 import org.opendaylight.controller.sal.authorization.IResourceAuthorization;
23 import org.opendaylight.controller.sal.authorization.Privilege;
24 import org.opendaylight.controller.sal.authorization.Resource;
25 import org.opendaylight.controller.sal.authorization.ResourceGroup;
26 import org.opendaylight.controller.sal.authorization.UserLevel;
27 import org.opendaylight.controller.sal.utils.ServiceHelper;
28 import org.opendaylight.controller.sal.utils.Status;
29 import org.opendaylight.controller.sal.utils.StatusCode;
30 import org.opendaylight.controller.usermanager.IUserManager;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35  * This abstract class implements the methods defined by IResourceAuthorization
36  * interface for each application class.
37  */
38 public abstract class Authorization<T> implements IResourceAuthorization {
39 private static final Logger logger = LoggerFactory.getLogger(Authorization.class);
40     private static final String namesRegex = ConfigurationObject.getRegularExpression();
41     /*
42      * The configured resource groups
43      */
44     protected ConcurrentMap<String, Set<T>> resourceGroups;
45     /*
46      * The configured roles along with their level
47      */
48     protected ConcurrentMap<String, AppRoleLevel> roles;
49     /*
50      * The association of groups to roles
51      */
52     protected ConcurrentMap<String, Set<ResourceGroup>> groupsAuthorizations;
53     /*
54      * The name of the default group. It is the group which contains all the
55      * resources
56      */
57     protected String allResourcesGroupName;
58
59     @Override
60     public Status createRole(String role, AppRoleLevel level) {
61         if (role == null || role.trim().isEmpty()
62                 || !role.matches(Authorization.namesRegex)) {
63             return new Status(StatusCode.BADREQUEST,
64                     "Role name must start with alphanumeric, no special characters.");
65         }
66         if (isControllerRole(role)) {
67             return new Status(StatusCode.NOTALLOWED,
68                     "Controller roles cannot be explicitely "
69                             + "created in App context");
70         }
71         if (isContainerRole(role)) {
72             return new Status(StatusCode.NOTALLOWED,
73                     "Container roles cannot be explicitely "
74                             + "created in App context");
75         }
76         if (isRoleInUse(role)) {
77             return new Status(StatusCode.CONFLICT, "Role already in use");
78         }
79         if (roles.containsKey(role)) {
80             if (roles.get(role).equals(level)) {
81                 return new Status(StatusCode.SUCCESS, "Role is already present");
82             } else {
83                 return new Status(StatusCode.BADREQUEST,
84                         "Role exists and has different level");
85             }
86         }
87
88         return createRoleInternal(role, level);
89     }
90
91     protected Status createRoleInternal(String role, AppRoleLevel level) {
92         roles.put(role, level);
93         groupsAuthorizations.put(role, new HashSet<ResourceGroup>());
94         return new Status(StatusCode.SUCCESS);
95     }
96
97     @Override
98     public Status removeRole(String role) {
99         if (role == null || role.trim().isEmpty()) {
100             return new Status(StatusCode.BADREQUEST, "Role name can't be empty");
101         }
102         if (isControllerRole(role)) {
103             return new Status(StatusCode.NOTALLOWED,
104                     "Controller roles cannot be removed");
105         }
106         if (isContainerRole(role)) {
107             return new Status(StatusCode.NOTALLOWED,
108                     "Container roles cannot be removed");
109         }
110         return removeRoleInternal(role);
111     }
112
113     protected Status removeRoleInternal(String role) {
114         groupsAuthorizations.remove(role);
115         roles.remove(role);
116         return new Status(StatusCode.SUCCESS);
117     }
118
119     @Override
120     public List<String> getRoles() {
121         return new ArrayList<String>(groupsAuthorizations.keySet());
122     }
123
124     @SuppressWarnings("unchecked")
125     @Override
126     public Status createResourceGroup(String groupName, List<Object> resources) {
127         //verify group name not null/empty
128         if (groupName == null || groupName.trim().isEmpty()
129                 || !groupName.matches(Authorization.namesRegex)) {
130             return new Status(StatusCode.BADREQUEST, "Group name must start with alphanumeric, no special characters");
131         }
132         //verify group name is not same as all-resources
133         if (groupName.equals(this.allResourcesGroupName)) {
134             return new Status(StatusCode.NOTALLOWED, "All resource group cannot be created");
135         }
136         //verify group name is unique
137         if (resourceGroups.containsKey(groupName)) {
138             return new Status(StatusCode.CONFLICT, "Group name already exists");
139         }
140
141         //try adding resources, discard if not of type T
142         Set<T> toBeAdded = new HashSet<T>();
143         boolean allAdded = true;
144         for (Object obj : resources) {
145             try {
146                 toBeAdded.add((T) obj);
147             } catch (ClassCastException e) {
148                 logger.debug("Attempt to add a resource with invalid type");
149                 allAdded = false;
150             }
151         }
152         resourceGroups.put(groupName, toBeAdded);
153         return (allAdded ? new Status(StatusCode.SUCCESS, "All resources added succesfully") :
154             new Status(StatusCode.SUCCESS, "One or more resources couldn't be added"));
155     }
156
157     @SuppressWarnings("unchecked")
158     @Override
159     public Status addResourceToGroup(String groupName, Object resource) {
160         if (groupName == null || groupName.trim().isEmpty()) {
161             return new Status(StatusCode.BADREQUEST, "Invalid group name");
162         }
163
164         if (resource == null) {
165             return new Status(StatusCode.BADREQUEST, "Null resource");
166         }
167
168         T castedResource = null;
169         try {
170             castedResource = (T) resource;
171         } catch (ClassCastException e) {
172             logger.debug("Attempt to add a resource with invalid type");
173             return new Status(StatusCode.BADREQUEST, "Incompatible resource");
174         }
175
176         Set<T> group = resourceGroups.get(groupName);
177         if (group == null) {
178             return new Status(StatusCode.NOTFOUND, "Group not found");
179         }
180
181         return addResourceToGroupInternal(groupName, castedResource);
182     }
183
184     /*
185      * Method child classes can overload if they need application specific
186      * checks on the resource
187      */
188     protected Status addResourceToGroupInternal(String groupName, T resource) {
189         Set<T> group = resourceGroups.get(groupName);
190         // Update group and cluster
191         group.add(resource);
192         resourceGroups.put(groupName, group);
193
194         return new Status(StatusCode.SUCCESS, "Resource added successfully");
195
196     }
197
198     private Status removeRoleResourceGroupMapping(String groupName) {
199         List<String> affectedRoles = new ArrayList<String>();
200         Status result;
201         for (Entry<String, Set<ResourceGroup>> pairs : groupsAuthorizations.entrySet()) {
202             String role = pairs.getKey();
203             Set<ResourceGroup> groups = pairs.getValue();
204             for (ResourceGroup group : groups) {
205                 if (group.getGroupName().equals(groupName)) {
206                     affectedRoles.add(role);
207                     break;
208                 }
209             }
210         }
211         StringBuffer msg = new StringBuffer();
212         for (String role : affectedRoles) {
213             result = unassignResourceGroupFromRole(groupName, role);
214             if (!result.isSuccess()) {
215                 msg.append(result.getDescription());
216                 msg.append(' ');
217             }
218         }
219
220         if (msg.length() != 0) {
221             return new Status(StatusCode.BADREQUEST, msg.toString());
222         } else {
223             return new Status(StatusCode.SUCCESS);
224         }
225     }
226
227     @Override
228     public Status removeResourceGroup(String groupName) {
229         // Default resource group cannot be deleted
230         if (groupName == null || groupName.trim().isEmpty()) {
231             return new Status(StatusCode.BADREQUEST, "Invalid group name");
232         }
233         if (groupName.equals(this.allResourcesGroupName)) {
234             return new Status(StatusCode.NOTALLOWED,
235                     "All resource group cannot be removed");
236         }
237         resourceGroups.remove(groupName);
238         Status result = removeRoleResourceGroupMapping(groupName);
239
240         return result.isSuccess() ? result :
241             new Status(StatusCode.SUCCESS, "Failed removing group from: " + result.getDescription());
242     }
243
244
245     @Override
246     public Status removeResourceFromGroup(String groupName, Object resource) {
247         if (groupName == null || groupName.trim().isEmpty()) {
248             return new Status(StatusCode.BADREQUEST, "Invalid group name");
249         }
250
251         Set<T> group = resourceGroups.get(groupName);
252         if (group != null && group.remove(resource)) {
253             // Update cluster
254             resourceGroups.put(groupName, group);
255             return new Status(StatusCode.SUCCESS, "Resource removed successfully");
256         }
257
258         return new Status(StatusCode.NOTFOUND, "Group/Resource not found");
259     }
260
261
262     /**
263      * Relay the call to user manager
264      */
265     @Override
266     public UserLevel getUserLevel(String userName) {
267         IUserManager userManager = (IUserManager) ServiceHelper
268                 .getGlobalInstance(IUserManager.class, this);
269
270         if (logger.isDebugEnabled()) {
271             logger.debug("User {} has UserLevel {}", userName, userManager.getUserLevel(userName));
272         }
273
274         return (userManager == null) ? UserLevel.NOUSER : userManager
275                 .getUserLevel(userName);
276     }
277
278     @Override
279     public Set<Resource> getAllResourcesforUser(String userName) {
280         Set<Resource> resources = new HashSet<Resource>();
281         IUserManager userManager = (IUserManager) ServiceHelper
282                 .getGlobalInstance(IUserManager.class, this);
283         if (userManager == null) {
284             return new HashSet<Resource>(0);
285         }
286
287         // Get the roles associated with this user
288         List<String> roles = userManager.getUserRoles(userName);
289         if (roles == null) {
290             return resources;
291         }
292
293         for (String role : roles) {
294             // Get our resource groups associated with this role
295             List<ResourceGroup> groups = this.getAuthorizedGroups(role);
296             if (groups.isEmpty()) {
297                 continue;
298             }
299             for (ResourceGroup group : groups) {
300                 // Get the list of resources in this group
301                 List<Object> list = this.getResources(group.getGroupName());
302                 if (list.isEmpty()) {
303                     continue;
304                 }
305                 for (Object resource : list) {
306                     Resource toBeAdded = new Resource(resource,
307                             group.getPrivilege());
308                     /*
309                      * Add the resource to the set if the same resource with
310                      * higher privilege is not present. If the same resource
311                      * with lower privilege is present, remove it. No check on
312                      * same privilege resource as set guarantees no duplicates.
313                      */
314                     Resource existing = higherPrivilegeResourcePresent(
315                             resources, toBeAdded);
316                     if (existing == null) {
317                         resources.add(toBeAdded);
318                     }
319                     existing = lowerPrivilegeResourcePresent(resources,
320                             toBeAdded);
321                     if (existing != null) {
322                         resources.remove(existing);
323                     }
324                 }
325             }
326         }
327
328         if (logger.isDebugEnabled()) {
329             logger.debug("User {} has resources {}", userName, resources);
330         }
331
332         return resources;
333     }
334
335     @Override
336     public List<Resource> getAuthorizedResources(String role) {
337         Set<Resource> resources = new HashSet<Resource>();
338
339         // Get our resource groups associated with this role
340         List<ResourceGroup> groups = this.getAuthorizedGroups(role);
341         if (groups.isEmpty()) {
342             return new ArrayList<Resource>(0);
343         }
344         for (ResourceGroup group : groups) {
345             // Get the list of resources in this group
346             List<Object> list = this.getResources(group.getGroupName());
347             if (list.isEmpty()) {
348                 continue;
349             }
350             for (Object resource : list) {
351                 Resource toBeAdded = new Resource(resource,
352                         group.getPrivilege());
353                 /*
354                  * Add the resource to the set if the same resource with higher
355                  * privilege is not present. If the same resource with lower
356                  * privilege is present, remove it. No check on same privilege
357                  * resource as set guarantees no duplicates.
358                  */
359                 Resource existing = higherPrivilegeResourcePresent(resources,
360                         toBeAdded);
361                 if (existing == null) {
362                     resources.add(toBeAdded);
363                 }
364                 existing = lowerPrivilegeResourcePresent(resources, toBeAdded);
365                 if (existing != null) {
366                     resources.remove(existing);
367                 }
368             }
369         }
370
371         if (logger.isDebugEnabled()) {
372             logger.debug("For the Role {}, Authorized Resources are {}", role, resources);
373         }
374
375         return new ArrayList<Resource>(resources);
376     }
377
378     /**
379      * Given a set of resources and a resource to test, it returns the element
380      * in the set which represent the same resource with a lower privilege
381      * associated to it, if present.
382      *
383      * @param resources
384      * @param resource
385      * @return
386      */
387     private Resource lowerPrivilegeResourcePresent(Set<Resource> resources,
388             Resource resource) {
389
390         if (resource == null || resources == null) {
391             return null;
392         }
393
394         Object resourceElement = resource.getResource();
395         Privilege resourcePrivilege = resource.getPrivilege();
396         for (Resource element : resources) {
397             if (element.getResource().equals(resourceElement)
398                     && element.getPrivilege().ordinal() < resourcePrivilege
399                             .ordinal()) {
400                 return element;
401             }
402         }
403         return null;
404     }
405
406     /**
407      * Given a set of resources and a resource to test, it returns the element
408      * in the set which represents the same resource with an higher privilege,
409      * if present.
410      *
411      * @param resources
412      * @param resource
413      * @return
414      */
415     private Resource higherPrivilegeResourcePresent(Set<Resource> resources,
416             Resource resource) {
417
418         if (resource == null || resources == null) {
419             return null;
420         }
421
422         Object resourceElement = resource.getResource();
423         Privilege resourcePrivilege = resource.getPrivilege();
424         for (Resource element : resources) {
425             if (element.getResource().equals(resourceElement)
426                     && element.getPrivilege().ordinal() > resourcePrivilege
427                             .ordinal()) {
428                 return element;
429             }
430         }
431
432         return null;
433     }
434
435     @Override
436     public Privilege getResourcePrivilege(String userName, Object resource) {
437
438         if (userName == null || userName.trim().isEmpty() || resource == null) {
439             return Privilege.NONE;
440         }
441
442         Set<Resource> hisResources = getAllResourcesforUser(userName);
443         for (Resource element : hisResources) {
444             if (element.getResource().equals(resource)) {
445                 return element.getPrivilege();
446             }
447         }
448
449         return Privilege.NONE;
450     }
451
452     @Override
453     public List<String> getResourceGroups() {
454         return new ArrayList<String>(resourceGroups.keySet());
455     }
456
457     @Override
458     public Status assignResourceGroupToRole(String group, Privilege privilege,
459             String role) {
460         if (group == null || group.trim().isEmpty()) {
461             return new Status(StatusCode.BADREQUEST, "Invalid group name");
462         }
463         if (role == null || role.trim().isEmpty()) {
464             return new Status(StatusCode.BADREQUEST, "Role name can't be empty");
465         }
466         if (isControllerRole(role)) {
467             return new Status(StatusCode.NOTALLOWED,
468                     "No group assignment is accepted for Controller roles");
469         }
470
471         return assignResourceGroupToRoleInternal(group, privilege, role);
472     }
473
474     protected Status assignResourceGroupToRoleInternal(String group, Privilege privilege, String role) {
475         Set<ResourceGroup> roleGroups = groupsAuthorizations.get(role);
476         roleGroups.add(new ResourceGroup(group, privilege));
477         // Update cluster
478         groupsAuthorizations.put(role, roleGroups);
479         return new Status(StatusCode.SUCCESS);
480     }
481
482     @Override
483     public Status assignResourceGroupToRole(String groupName, String roleName) {
484         if (groupName == null || groupName.trim().isEmpty()) {
485             return new Status(StatusCode.BADREQUEST, "Group name can't be empty");
486         }
487         if (roleName == null || roleName.trim().isEmpty()) {
488             return new Status(StatusCode.BADREQUEST, "Role name can't be empty");
489         }
490         // Infer group privilege from role's level
491         Privilege privilege = Privilege.NONE;
492         switch (this.getApplicationRoleLevel(roleName)) {
493         case APPADMIN:
494             privilege = Privilege.WRITE;
495             break;
496         case APPUSER:
497             privilege = Privilege.USE;
498             break;
499         case APPOPERATOR:
500             privilege = Privilege.READ;
501             break;
502         default:
503             break;
504         }
505         return this.assignResourceGroupToRole(groupName, privilege, roleName);
506     }
507
508     @Override
509     public Status unassignResourceGroupFromRole(String group, String role) {
510         if (group == null || group.trim().isEmpty()) {
511             return new Status(StatusCode.BADREQUEST, "Group name can't be empty");
512         }
513         if (role == null || role.trim().isEmpty()) {
514             return new Status(StatusCode.BADREQUEST, "Role name can't be empty");
515         }
516         if (isControllerRole(role)) {
517             return new Status(StatusCode.NOTALLOWED,
518                     "No group assignment change is allowed for "
519                             + "Controller roles");
520         }
521
522         return unassignResourceGroupFromRoleInternal(group, role);
523     }
524
525     protected Status unassignResourceGroupFromRoleInternal(String group, String role) {
526         ResourceGroup target = null;
527         for (ResourceGroup rGroup : groupsAuthorizations.get(role)) {
528             if (rGroup.getGroupName().equals(group)) {
529                 target = rGroup;
530                 break;
531             }
532         }
533         if (target == null) {
534             return new Status(StatusCode.SUCCESS, "Group " + group + " was not assigned to " + role);
535         } else {
536             Set<ResourceGroup> groups = groupsAuthorizations.get(role);
537             groups.remove(target);
538             // Update cluster
539             groupsAuthorizations.put(role, groups);
540             return new Status(StatusCode.SUCCESS);
541
542         }
543     }
544
545     @Override
546     public List<ResourceGroup> getAuthorizedGroups(String role) {
547         return (groupsAuthorizations.containsKey(role)) ? new ArrayList<ResourceGroup>(
548                 groupsAuthorizations.get(role))
549                 : new ArrayList<ResourceGroup>();
550     }
551
552     @Override
553     public List<Object> getResources(String groupName) {
554         return (resourceGroups.containsKey(groupName)) ? new ArrayList<Object>(
555                 resourceGroups.get(groupName)) : new ArrayList<Object>(0);
556     }
557
558     @Override
559     public boolean isApplicationRole(String roleName) {
560         if (roleName == null) {
561             return false;
562         }
563         return roles.containsKey(roleName);
564     }
565
566     @Override
567     public boolean isApplicationUser(String userName) {
568         IUserManager userManager = (IUserManager) ServiceHelper
569                 .getGlobalInstance(IUserManager.class, this);
570         if (userManager == null) {
571             return false;
572         }
573         List<String> roles = userManager.getUserRoles(userName);
574         if (roles != null && !roles.isEmpty()) {
575             for (String role : roles) {
576                 if (isApplicationRole(role)) {
577                     return true;
578                 }
579             }
580         }
581         return false;
582     }
583
584     @Override
585     public AppRoleLevel getApplicationRoleLevel(String roleName) {
586         if (roleName == null || roleName.trim().isEmpty()) {
587             return AppRoleLevel.NOUSER;
588         }
589
590         if (isControllerRole(roleName)) {
591             if (roleName.equals(UserLevel.NETWORKADMIN.toString()) ||
592                     roleName.equals(UserLevel.SYSTEMADMIN.toString())) {
593                 return AppRoleLevel.APPADMIN;
594             } else {
595                 return AppRoleLevel.APPOPERATOR;
596             }
597         }
598
599         return (roles.containsKey(roleName)) ? roles.get(roleName)
600                 : AppRoleLevel.NOUSER;
601     }
602
603     @Override
604     public AppRoleLevel getUserApplicationLevel(String userName) {
605         List<String> roles = null;
606         IUserManager userManager = (IUserManager) ServiceHelper
607                 .getGlobalInstance(IUserManager.class, this);
608
609         if (userName == null || userName.trim().isEmpty()
610                 || (userManager == null)
611                 || (roles = userManager.getUserRoles(userName)).isEmpty()) {
612             return AppRoleLevel.NOUSER;
613         }
614         AppRoleLevel highestLevel = AppRoleLevel.NOUSER;
615         for (String role : roles) {
616             AppRoleLevel level = getApplicationRoleLevel(role);
617             if (level.ordinal() < highestLevel.ordinal()) {
618                 highestLevel = level;
619             }
620         }
621         return highestLevel;
622     }
623
624     /**
625      * Returns the highest role the specified user has in this application
626      * context
627      *
628      * @param user
629      *            The user name
630      * @return The highest role associated to the user in this application
631      *         context
632      */
633     public String getHighestUserRole(String user) {
634         IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this);
635         String highestRole = "";
636         if (userManager != null && !userManager.getUserRoles(user).isEmpty()) {
637             List<String> roles = userManager.getUserRoles(user);
638             AppRoleLevel highestLevel = AppRoleLevel.NOUSER;
639             for (String role : roles) {
640                 AppRoleLevel current;
641                 if (isApplicationRole(role)
642                         && (current = getApplicationRoleLevel(role)).ordinal() < highestLevel.ordinal()) {
643                     highestRole = role;
644                     highestLevel = current;
645                 }
646             }
647         }
648         return highestRole;
649     }
650
651     private boolean isControllerRole(String role) {
652         return (role.equals(UserLevel.NETWORKADMIN.toString())
653                 || role.equals(UserLevel.SYSTEMADMIN.toString()) || role
654                     .equals(UserLevel.NETWORKOPERATOR.toString()));
655     }
656
657     private boolean isContainerRole(String role) {
658         IContainerAuthorization containerAuth = (IContainerAuthorization) ServiceHelper.getGlobalInstance(
659                 IContainerAuthorization.class, this);
660         if (containerAuth == null) {
661             return false;
662         }
663         return containerAuth.isApplicationRole(role);
664     }
665
666     private boolean isRoleInUse(String role) {
667         IUserManager userManager = (IUserManager) ServiceHelper
668                 .getGlobalInstance(IUserManager.class, this);
669
670         if (userManager == null) {
671             return true;
672         }
673         return userManager.isRoleInUse(role);
674     }
675 }