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