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