b872f49130205d239f9ee9bfac647b09eb7a241e
[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                 allAdded = false;
148             }
149         }
150         resourceGroups.put(groupName, toBeAdded);
151         return (allAdded ? new Status(StatusCode.SUCCESS, "All resources added succesfully") :
152             new Status(StatusCode.SUCCESS, "One or more resources couldn't be added"));
153     }
154
155     public Status addResourceToGroup(String groupName, T resource) {
156         if (groupName == null || groupName.trim().isEmpty()) {
157             return new Status(StatusCode.BADREQUEST, "Invalid group name");
158         }
159
160         Set<T> group = resourceGroups.get(groupName);
161         if (group != null && resource != null) {
162             group.add(resource);
163             // Update cluster
164             resourceGroups.put(groupName, group);
165             return new Status(StatusCode.SUCCESS, "Resource added successfully");
166         }
167
168         return new Status(StatusCode.NOTFOUND, "Group not found or incompatible resource");
169     }
170
171     public Status removeRoleResourceGroupMapping(String groupName) {
172         List<String> affectedRoles = new ArrayList<String>();
173         Status result;
174         for (Entry<String, Set<ResourceGroup>> pairs : groupsAuthorizations.entrySet()) {
175             String role = pairs.getKey();
176             Set<ResourceGroup> groups = pairs.getValue();
177             for (ResourceGroup group : groups) {
178                 if (group.getGroupName().equals(groupName)) {
179                     affectedRoles.add(role);
180                     break;
181                 }
182             }
183         }
184         StringBuffer msg = new StringBuffer();
185         for (String role : affectedRoles) {
186             result = unassignResourceGroupFromRole(groupName, role);
187             if (!result.isSuccess()) {
188                 msg.append(result.getDescription());
189                 msg.append(' ');
190             }
191         }
192
193         if (msg.length() != 0) {
194             return new Status(StatusCode.BADREQUEST, msg.toString());
195         } else {
196             return new Status(StatusCode.SUCCESS);
197         }
198     }
199
200     @Override
201     public Status removeResourceGroup(String groupName) {
202         // Default resource group cannot be deleted
203         if (groupName == null || groupName.trim().isEmpty()) {
204             return new Status(StatusCode.BADREQUEST, "Invalid group name");
205         }
206         if (groupName.equals(this.allResourcesGroupName)) {
207             return new Status(StatusCode.NOTALLOWED,
208                     "All resource group cannot be removed");
209         }
210         resourceGroups.remove(groupName);
211         Status result = removeRoleResourceGroupMapping(groupName);
212
213         return result.isSuccess() ? result :
214             new Status(StatusCode.SUCCESS, "Failed removing group from: " + result.getDescription());
215     }
216
217
218     public Status removeResourceFromGroup(String groupName, T resource) {
219         if (groupName == null || groupName.trim().isEmpty()) {
220             return new Status(StatusCode.BADREQUEST, "Invalid group name");
221         }
222
223         Set<T> group = resourceGroups.get(groupName);
224         if (group != null && group.remove(resource)) {
225             // Update cluster
226             resourceGroups.put(groupName, group);
227             return new Status(StatusCode.SUCCESS, "Resource removed successfully");
228         }
229
230         return new Status(StatusCode.NOTFOUND, "Group/Resource not found");
231     }
232
233
234     /**
235      * Relay the call to user manager
236      */
237     @Override
238     public UserLevel getUserLevel(String userName) {
239         IUserManager userManager = (IUserManager) ServiceHelper
240                 .getGlobalInstance(IUserManager.class, this);
241
242         if (logger.isDebugEnabled()) {
243             logger.debug("User {} has UserLevel {}", userName, userManager.getUserLevel(userName));
244         }
245
246         return (userManager == null) ? UserLevel.NOUSER : userManager
247                 .getUserLevel(userName);
248     }
249
250     @Override
251     public Set<Resource> getAllResourcesforUser(String userName) {
252         Set<Resource> resources = new HashSet<Resource>();
253         IUserManager userManager = (IUserManager) ServiceHelper
254                 .getGlobalInstance(IUserManager.class, this);
255         if (userManager == null) {
256             return new HashSet<Resource>(0);
257         }
258
259         // Get the roles associated with this user
260         List<String> roles = userManager.getUserRoles(userName);
261         if (roles == null) {
262             return resources;
263         }
264
265         for (String role : roles) {
266             // Get our resource groups associated with this role
267             List<ResourceGroup> groups = this.getAuthorizedGroups(role);
268             if (groups.isEmpty()) {
269                 continue;
270             }
271             for (ResourceGroup group : groups) {
272                 // Get the list of resources in this group
273                 List<Object> list = this.getResources(group.getGroupName());
274                 if (list.isEmpty()) {
275                     continue;
276                 }
277                 for (Object resource : list) {
278                     Resource toBeAdded = new Resource(resource,
279                             group.getPrivilege());
280                     /*
281                      * Add the resource to the set if the same resource with
282                      * higher privilege is not present. If the same resource
283                      * with lower privilege is present, remove it. No check on
284                      * same privilege resource as set guarantees no duplicates.
285                      */
286                     Resource existing = higherPrivilegeResourcePresent(
287                             resources, toBeAdded);
288                     if (existing == null) {
289                         resources.add(toBeAdded);
290                     }
291                     existing = lowerPrivilegeResourcePresent(resources,
292                             toBeAdded);
293                     if (existing != null) {
294                         resources.remove(existing);
295                     }
296                 }
297             }
298         }
299
300         if (logger.isDebugEnabled()) {
301             logger.debug("User {} has resources {}", userName, resources);
302         }
303
304         return resources;
305     }
306
307     @Override
308     public List<Resource> getAuthorizedResources(String role) {
309         Set<Resource> resources = new HashSet<Resource>();
310
311         // Get our resource groups associated with this role
312         List<ResourceGroup> groups = this.getAuthorizedGroups(role);
313         if (groups.isEmpty()) {
314             return new ArrayList<Resource>(0);
315         }
316         for (ResourceGroup group : groups) {
317             // Get the list of resources in this group
318             List<Object> list = this.getResources(group.getGroupName());
319             if (list.isEmpty()) {
320                 continue;
321             }
322             for (Object resource : list) {
323                 Resource toBeAdded = new Resource(resource,
324                         group.getPrivilege());
325                 /*
326                  * Add the resource to the set if the same resource with higher
327                  * privilege is not present. If the same resource with lower
328                  * privilege is present, remove it. No check on same privilege
329                  * resource as set guarantees no duplicates.
330                  */
331                 Resource existing = higherPrivilegeResourcePresent(resources,
332                         toBeAdded);
333                 if (existing == null) {
334                     resources.add(toBeAdded);
335                 }
336                 existing = lowerPrivilegeResourcePresent(resources, toBeAdded);
337                 if (existing != null) {
338                     resources.remove(existing);
339                 }
340             }
341         }
342
343         if (logger.isDebugEnabled()) {
344             logger.debug("For the Role {}, Authorized Resources are {}", role, resources);
345         }
346
347         return new ArrayList<Resource>(resources);
348     }
349
350     /**
351      * Given a set of resources and a resource to test, it returns the element
352      * in the set which represent the same resource with a lower privilege
353      * associated to it, if present.
354      *
355      * @param resources
356      * @param resource
357      * @return
358      */
359     private Resource lowerPrivilegeResourcePresent(Set<Resource> resources,
360             Resource resource) {
361
362         if (resource == null || resources == null) {
363             return null;
364         }
365
366         Object resourceElement = resource.getResource();
367         Privilege resourcePrivilege = resource.getPrivilege();
368         for (Resource element : resources) {
369             if (element.getResource().equals(resourceElement)
370                     && element.getPrivilege().ordinal() < resourcePrivilege
371                             .ordinal()) {
372                 return element;
373             }
374         }
375         return null;
376     }
377
378     /**
379      * Given a set of resources and a resource to test, it returns the element
380      * in the set which represents the same resource with an higher privilege,
381      * if present.
382      *
383      * @param resources
384      * @param resource
385      * @return
386      */
387     private Resource higherPrivilegeResourcePresent(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
404         return null;
405     }
406
407     @Override
408     public Privilege getResourcePrivilege(String userName, Object resource) {
409
410         if (userName == null || userName.trim().isEmpty() || resource == null) {
411             return Privilege.NONE;
412         }
413
414         Set<Resource> hisResources = getAllResourcesforUser(userName);
415         for (Resource element : hisResources) {
416             if (element.getResource().equals(resource)) {
417                 return element.getPrivilege();
418             }
419         }
420
421         return Privilege.NONE;
422     }
423
424     @Override
425     public List<String> getResourceGroups() {
426         return new ArrayList<String>(resourceGroups.keySet());
427     }
428
429     @Override
430     public Status assignResourceGroupToRole(String group, Privilege privilege,
431             String role) {
432         if (group == null || group.trim().isEmpty()) {
433             return new Status(StatusCode.BADREQUEST, "Invalid group name");
434         }
435         if (role == null || role.trim().isEmpty()) {
436             return new Status(StatusCode.BADREQUEST, "Role name can't be empty");
437         }
438         if (isControllerRole(role)) {
439             return new Status(StatusCode.NOTALLOWED,
440                     "No group assignment is accepted for Controller roles");
441         }
442
443         return assignResourceGroupToRoleInternal(group, privilege, role);
444     }
445
446     protected Status assignResourceGroupToRoleInternal(String group, Privilege privilege, String role) {
447         Set<ResourceGroup> roleGroups = groupsAuthorizations.get(role);
448         roleGroups.add(new ResourceGroup(group, privilege));
449         // Update cluster
450         groupsAuthorizations.put(role, roleGroups);
451         return new Status(StatusCode.SUCCESS);
452     }
453
454     @Override
455     public Status assignResourceGroupToRole(String groupName, String roleName) {
456         if (groupName == null || groupName.trim().isEmpty()) {
457             return new Status(StatusCode.BADREQUEST, "Group name can't be empty");
458         }
459         if (roleName == null || roleName.trim().isEmpty()) {
460             return new Status(StatusCode.BADREQUEST, "Role name can't be empty");
461         }
462         // Infer group privilege from role's level
463         Privilege privilege = Privilege.NONE;
464         switch (this.getApplicationRoleLevel(roleName)) {
465         case APPADMIN:
466             privilege = Privilege.WRITE;
467             break;
468         case APPUSER:
469             privilege = Privilege.USE;
470             break;
471         case APPOPERATOR:
472             privilege = Privilege.READ;
473             break;
474         default:
475             break;
476         }
477         return this.assignResourceGroupToRole(groupName, privilege, roleName);
478     }
479
480     @Override
481     public Status unassignResourceGroupFromRole(String group, String role) {
482         if (group == null || group.trim().isEmpty()) {
483             return new Status(StatusCode.BADREQUEST, "Group name can't be empty");
484         }
485         if (role == null || role.trim().isEmpty()) {
486             return new Status(StatusCode.BADREQUEST, "Role name can't be empty");
487         }
488         if (isControllerRole(role)) {
489             return new Status(StatusCode.NOTALLOWED,
490                     "No group assignment change is allowed for "
491                             + "Controller roles");
492         }
493
494         return unassignResourceGroupFromRoleInternal(group, role);
495     }
496
497     protected Status unassignResourceGroupFromRoleInternal(String group, String role) {
498         ResourceGroup target = null;
499         for (ResourceGroup rGroup : groupsAuthorizations.get(role)) {
500             if (rGroup.getGroupName().equals(group)) {
501                 target = rGroup;
502                 break;
503             }
504         }
505         if (target == null) {
506             return new Status(StatusCode.SUCCESS, "Group " + group + " was not assigned to " + role);
507         } else {
508             Set<ResourceGroup> groups = groupsAuthorizations.get(role);
509             groups.remove(target);
510             // Update cluster
511             groupsAuthorizations.put(role, groups);
512             return new Status(StatusCode.SUCCESS);
513
514         }
515     }
516
517     @Override
518     public List<ResourceGroup> getAuthorizedGroups(String role) {
519         return (groupsAuthorizations.containsKey(role)) ? new ArrayList<ResourceGroup>(
520                 groupsAuthorizations.get(role))
521                 : new ArrayList<ResourceGroup>();
522     }
523
524     @Override
525     public List<Object> getResources(String groupName) {
526         return (resourceGroups.containsKey(groupName)) ? new ArrayList<Object>(
527                 resourceGroups.get(groupName)) : new ArrayList<Object>(0);
528     }
529
530     @Override
531     public boolean isApplicationRole(String roleName) {
532         if (roleName == null) {
533             return false;
534         }
535         return roles.containsKey(roleName);
536     }
537
538     @Override
539     public AppRoleLevel getApplicationRoleLevel(String roleName) {
540         if (roleName == null || roleName.trim().isEmpty()) {
541             return AppRoleLevel.NOUSER;
542         }
543
544         if (isControllerRole(roleName)) {
545             if (roleName.equals(UserLevel.NETWORKADMIN.toString()) ||
546                     roleName.equals(UserLevel.SYSTEMADMIN.toString())) {
547                 return AppRoleLevel.APPADMIN;
548             } else {
549                 return AppRoleLevel.APPOPERATOR;
550             }
551         }
552
553         return (roles.containsKey(roleName)) ? roles.get(roleName)
554                 : AppRoleLevel.NOUSER;
555     }
556
557     @Override
558     public AppRoleLevel getUserApplicationLevel(String userName) {
559         List<String> roles = null;
560         IUserManager userManager = (IUserManager) ServiceHelper
561                 .getGlobalInstance(IUserManager.class, this);
562
563         if (userName == null || userName.trim().isEmpty()
564                 || (userManager == null)
565                 || (roles = userManager.getUserRoles(userName)).isEmpty()) {
566             return AppRoleLevel.NOUSER;
567         }
568         AppRoleLevel highestLevel = AppRoleLevel.NOUSER;
569         for (String role : roles) {
570             AppRoleLevel level = getApplicationRoleLevel(role);
571             if (level.ordinal() < highestLevel.ordinal()) {
572                 highestLevel = level;
573             }
574         }
575         return highestLevel;
576     }
577
578     /**
579      * Returns the highest role the specified user has in this application
580      * context
581      *
582      * @param user
583      *            The user name
584      * @return The highest role associated to the user in this application
585      *         context
586      */
587     public String getHighestUserRole(String user) {
588         IUserManager userManager = (IUserManager) ServiceHelper.getGlobalInstance(IUserManager.class, this);
589         String highestRole = "";
590         if (userManager != null && !userManager.getUserRoles(user).isEmpty()) {
591             List<String> roles = userManager.getUserRoles(user);
592             AppRoleLevel highestLevel = AppRoleLevel.NOUSER;
593             for (String role : roles) {
594                 AppRoleLevel current;
595                 if (isApplicationRole(role)
596                         && (current = getApplicationRoleLevel(role)).ordinal() < highestLevel.ordinal()) {
597                     highestRole = role;
598                     highestLevel = current;
599                 }
600             }
601         }
602         return highestRole;
603     }
604
605     private boolean isControllerRole(String role) {
606         return (role.equals(UserLevel.NETWORKADMIN.toString())
607                 || role.equals(UserLevel.SYSTEMADMIN.toString()) || role
608                     .equals(UserLevel.NETWORKOPERATOR.toString()));
609     }
610
611     private boolean isContainerRole(String role) {
612         IContainerAuthorization containerAuth = (IContainerAuthorization) ServiceHelper.getGlobalInstance(
613                 IContainerAuthorization.class, this);
614         if (containerAuth == null) {
615             return false;
616         }
617         return containerAuth.isApplicationRole(role);
618     }
619
620     private boolean isRoleInUse(String role) {
621         IUserManager userManager = (IUserManager) ServiceHelper
622                 .getGlobalInstance(IUserManager.class, this);
623
624         if (userManager == null) {
625             return true;
626         }
627         return userManager.isRoleInUse(role);
628     }
629 }