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