001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kim.impl.permission;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.commons.lang.exception.ExceptionUtils;
021import org.apache.log4j.Logger;
022import org.kuali.rice.core.api.cache.CacheKeyUtils;
023import org.kuali.rice.core.api.criteria.CriteriaLookupService;
024import org.kuali.rice.core.api.criteria.GenericQueryResults;
025import org.kuali.rice.core.api.criteria.LookupCustomizer;
026import org.kuali.rice.core.api.criteria.QueryByCriteria;
027import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
028import org.kuali.rice.core.api.exception.RiceIllegalStateException;
029import org.kuali.rice.core.api.membership.MemberType;
030import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
031import org.kuali.rice.kim.api.KimConstants;
032import org.kuali.rice.kim.api.common.assignee.Assignee;
033import org.kuali.rice.kim.api.common.delegate.DelegateType;
034import org.kuali.rice.kim.api.common.template.Template;
035import org.kuali.rice.kim.api.common.template.TemplateQueryResults;
036import org.kuali.rice.kim.api.identity.principal.Principal;
037import org.kuali.rice.kim.api.permission.Permission;
038import org.kuali.rice.kim.api.permission.PermissionQueryResults;
039import org.kuali.rice.kim.api.permission.PermissionService;
040import org.kuali.rice.kim.api.role.RoleMembership;
041import org.kuali.rice.kim.api.role.RoleService;
042import org.kuali.rice.kim.api.services.KimApiServiceLocator;
043import org.kuali.rice.kim.api.type.KimType;
044import org.kuali.rice.kim.api.type.KimTypeInfoService;
045import org.kuali.rice.kim.framework.permission.PermissionTypeService;
046import org.kuali.rice.kim.impl.common.attribute.AttributeTransform;
047import org.kuali.rice.kim.impl.common.attribute.KimAttributeDataBo;
048import org.kuali.rice.kim.impl.role.RolePermissionBo;
049import org.kuali.rice.krad.service.BusinessObjectService;
050import org.kuali.rice.krad.util.KRADPropertyConstants;
051import org.springframework.cache.Cache;
052import org.springframework.cache.CacheManager;
053import org.springframework.cache.support.NoOpCacheManager;
054
055import javax.xml.namespace.QName;
056import java.util.ArrayList;
057import java.util.Collection;
058import java.util.Collections;
059import java.util.Comparator;
060import java.util.HashMap;
061import java.util.List;
062import java.util.Map;
063import java.util.concurrent.CopyOnWriteArrayList;
064
065import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
066import static org.kuali.rice.core.api.criteria.PredicateFactory.in;
067
068public class PermissionServiceImpl implements PermissionService {
069    private static final Logger LOG = Logger.getLogger( PermissionServiceImpl.class );
070
071        private RoleService roleService;
072    private PermissionTypeService defaultPermissionTypeService;
073    private KimTypeInfoService kimTypeInfoService;
074        private BusinessObjectService businessObjectService;
075        private CriteriaLookupService criteriaLookupService;
076    private CacheManager cacheManager;
077
078        private final CopyOnWriteArrayList<Template> allTemplates = new CopyOnWriteArrayList<Template>();
079
080    // --------------------
081    // Authorization Checks
082    // --------------------
083
084    public PermissionServiceImpl() {
085        this.cacheManager = new NoOpCacheManager();
086    }
087
088    protected PermissionTypeService getPermissionTypeService(Template permissionTemplate) {
089        if ( permissionTemplate == null ) {
090                throw new IllegalArgumentException( "permissionTemplate may not be null" );
091        }
092        KimType kimType = kimTypeInfoService.getKimType( permissionTemplate.getKimTypeId() );
093        String serviceName = kimType.getServiceName();
094        // if no service specified, return a default implementation
095        if ( StringUtils.isBlank( serviceName ) ) {
096                return defaultPermissionTypeService;
097        }
098        try {
099                Object service = GlobalResourceLoader.getService(QName.valueOf(serviceName));
100                // if we have a service name, it must exist
101                if ( service == null ) {
102                                throw new RuntimeException("null returned for permission type service for service name: " + serviceName);
103                }
104                // whatever we retrieved must be of the correct type
105                if ( !(service instanceof PermissionTypeService)  ) {
106                        throw new RuntimeException( "Service " + serviceName + " was not a PermissionTypeService.  Was: " + service.getClass().getName() );
107                }
108                return (PermissionTypeService)service;
109        } catch( Exception ex ) {
110                // sometimes service locators throw exceptions rather than returning null, handle that
111                throw new RuntimeException( "Error retrieving service: " + serviceName + " from the KimImplServiceLocator.", ex );
112        }
113    }
114
115    @Override
116    public boolean hasPermission(String principalId, String namespaceCode,
117                                 String permissionName) throws RiceIllegalArgumentException  {
118        incomingParamCheck(principalId, "principalId");
119        incomingParamCheck(namespaceCode, "namespaceCode");
120        incomingParamCheck(permissionName, "permissionName");
121
122        return isAuthorized( principalId, namespaceCode, permissionName, Collections.<String, String>emptyMap() );
123    }
124
125    @Override
126    public boolean isAuthorized(String principalId, String namespaceCode,
127                                String permissionName, Map<String, String> qualification ) throws RiceIllegalArgumentException {
128        incomingParamCheck(principalId, "principalId");
129        incomingParamCheck(namespaceCode, "namespaceCode");
130        incomingParamCheck(permissionName, "permissionName");
131        incomingParamCheck(qualification, "qualification");
132
133        if ( LOG.isDebugEnabled() ) {
134            logAuthorizationCheck("Permission", principalId, namespaceCode, permissionName, qualification);
135        }
136
137        List<String> roleIds = getRoleIdsForPermission(namespaceCode, permissionName);
138        if ( roleIds.isEmpty() ) {
139                if ( LOG.isDebugEnabled() ) {
140                        LOG.debug( "Result: false");
141                }
142                return false;
143        }
144
145        boolean isAuthorized = roleService.principalHasRole(principalId, roleIds, qualification);
146        
147        if ( LOG.isDebugEnabled() ) {
148            LOG.debug( "Result: " + isAuthorized );
149        }
150        return isAuthorized;
151
152    }
153    @Override
154    public boolean hasPermissionByTemplate(String principalId, String namespaceCode, String permissionTemplateName,
155            Map<String, String> permissionDetails) throws RiceIllegalArgumentException {
156        incomingParamCheck(principalId, "principalId");
157        incomingParamCheck(namespaceCode, "namespaceCode");
158        incomingParamCheck(permissionTemplateName, "permissionTemplateName");
159
160        return isAuthorizedByTemplate(principalId, namespaceCode, permissionTemplateName, permissionDetails,
161                Collections.<String, String>emptyMap());
162    }
163    @Override
164    public boolean isAuthorizedByTemplate(String principalId, String namespaceCode, String permissionTemplateName,
165            Map<String, String> permissionDetails, Map<String, String> qualification) throws RiceIllegalArgumentException {
166        incomingParamCheck(principalId, "principalId");
167        incomingParamCheck(namespaceCode, "namespaceCode");
168        incomingParamCheck(permissionTemplateName, "permissionTemplateName");
169        incomingParamCheck(qualification, "qualification");
170
171        if ( LOG.isDebugEnabled() ) {
172            logAuthorizationCheckByTemplate("Perm Templ", principalId, namespaceCode, permissionTemplateName, permissionDetails, qualification);
173        }
174
175        List<String> roleIds = getRoleIdsForPermissionTemplate(namespaceCode, permissionTemplateName, permissionDetails);
176        if (roleIds.isEmpty()) {
177            if (LOG.isDebugEnabled()) {
178                LOG.debug("Result: false");
179            }
180                return false;
181        }
182        boolean isAuthorized = roleService.principalHasRole(principalId, roleIds, qualification);
183        if (LOG.isDebugEnabled()) {
184            LOG.debug( "Result: " + isAuthorized );
185        }
186                return isAuthorized;
187        
188    }
189    @Override
190    public List<Permission> getAuthorizedPermissions( String principalId,
191            String namespaceCode, String permissionName,
192            Map<String, String> qualification ) throws RiceIllegalArgumentException {
193        incomingParamCheck(principalId, "principalId");
194        incomingParamCheck(namespaceCode, "namespaceCode");
195        incomingParamCheck(permissionName, "permissionName");
196        incomingParamCheck(qualification, "qualification");
197
198        // get all the permission objects whose name match that requested
199        List<Permission> permissions = getPermissionsByName(namespaceCode, permissionName);
200        // now, filter the full list by the detail passed
201        List<Permission> applicablePermissions = getMatchingPermissions( permissions, null );
202        List<Permission> permissionsForUser = getPermissionsForUser(principalId, applicablePermissions, qualification);
203        return permissionsForUser;
204    }
205    @Override
206    public List<Permission> getAuthorizedPermissionsByTemplate(String principalId, String namespaceCode,
207            String permissionTemplateName, Map<String, String> permissionDetails, Map<String, String> qualification) throws RiceIllegalArgumentException {
208        incomingParamCheck(principalId, "principalId");
209        incomingParamCheck(namespaceCode, "namespaceCode");
210        incomingParamCheck(permissionTemplateName, "permissionTemplateName");
211        incomingParamCheck(qualification, "qualification");
212
213        // get all the permission objects whose name match that requested
214        List<Permission> permissions = getPermissionsByTemplateName(namespaceCode, permissionTemplateName);
215        // now, filter the full list by the detail passed
216        List<Permission> applicablePermissions = getMatchingPermissions( permissions, permissionDetails );
217
218        return getPermissionsForUser(principalId, applicablePermissions, qualification);
219    }
220    
221    /**
222     * Checks the list of permissions against the principal's roles and returns a subset of the list which match.
223     */
224    protected List<Permission> getPermissionsForUser( String principalId, List<Permission> permissions,
225            Map<String, String> qualification ) {
226        List<Permission> results = new ArrayList<Permission>();
227        for ( Permission perm : permissions ) {
228                List<String> roleIds = getRoleIdsForPermissions( Collections.singletonList(perm) );
229                if ( roleIds != null && !roleIds.isEmpty() ) {
230                        if ( roleService.principalHasRole( principalId, roleIds, qualification) ) {
231                                results.add( perm );
232                        }
233                }
234        }
235        return Collections.unmodifiableList(results);
236    }
237
238    protected Map<String,PermissionTypeService> getPermissionTypeServicesByTemplateId( Collection<Permission> permissions ) {
239        Map<String,PermissionTypeService> permissionTypeServices = new HashMap<String, PermissionTypeService>( permissions.size() );
240        for (Permission perm : permissions) {
241            if(!permissionTypeServices.containsKey(perm.getTemplate().getId())) {
242                permissionTypeServices.put(perm.getTemplate().getId(), getPermissionTypeService(perm.getTemplate()));
243            }
244        }
245        return permissionTypeServices;
246    }
247    
248        protected Map<String,List<Permission>> groupPermissionsByTemplate(Collection<Permission> permissions) {
249        Map<String,List<Permission>> results = new HashMap<String,List<Permission>>();
250        for (Permission perm : permissions) {
251                List<Permission> perms = results.get(perm.getTemplate().getId());
252                if (perms == null) {
253                        perms = new ArrayList<Permission>();
254                        results.put(perm.getTemplate().getId(), perms);
255                }
256                perms.add(perm);
257        }
258        return results;
259    }
260    
261        /**
262     * Compare each of the passed in permissions with the given permissionDetails.  Those that
263     * match are added to the result list.
264     */
265    protected List<Permission> getMatchingPermissions( List<Permission> permissions, Map<String, String> permissionDetails ) {
266        List<String> permissionIds = new ArrayList<String>(permissions.size());
267        for (Permission permission : permissions) {
268            permissionIds.add(permission.getId());
269        }
270        String cacheKey =  new StringBuilder("{getMatchingPermissions}")
271                .append("permissionIds=").append(CacheKeyUtils.key(permissionIds)).append("|")
272                .append("permissionDetails=").append(CacheKeyUtils.mapKey(permissionDetails)).toString();
273        Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
274        if (cachedValue != null && cachedValue.get() instanceof List) {
275            return ((List<Permission>)cachedValue.get());
276        }
277
278        List<Permission> applicablePermissions = new ArrayList<Permission>();           
279        if ( permissionDetails == null || permissionDetails.isEmpty() ) {
280                // if no details passed, assume that all match
281                for ( Permission perm : permissions ) {
282                        applicablePermissions.add(perm);
283                }
284        } else {
285                // otherwise, attempt to match the permission details
286                // build a map of the template IDs to the type services
287                Map<String,PermissionTypeService> permissionTypeServices = getPermissionTypeServicesByTemplateId(permissions);
288                // build a map of permissions by template ID
289                Map<String, List<Permission>> permissionMap = groupPermissionsByTemplate(permissions);
290                // loop over the different templates, matching all of the same template against the type
291                // service at once
292                for ( Map.Entry<String,List<Permission>> entry : permissionMap.entrySet() ) {
293                        PermissionTypeService permissionTypeService = permissionTypeServices.get( entry.getKey() );
294                        List<Permission> permissionList = entry.getValue();
295                                applicablePermissions.addAll( permissionTypeService.getMatchingPermissions( permissionDetails, permissionList ) );                              
296                }
297        }
298        applicablePermissions = Collections.unmodifiableList(applicablePermissions);
299        cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, applicablePermissions);
300        return applicablePermissions;
301    }
302
303
304    @Override
305    public List<Assignee> getPermissionAssignees( String namespaceCode, String permissionName,
306            Map<String, String> qualification ) throws RiceIllegalArgumentException {
307        incomingParamCheck(namespaceCode, "namespaceCode");
308        incomingParamCheck(permissionName, "permissionName");
309        incomingParamCheck(qualification, "qualification");
310
311        List<String> roleIds = getRoleIdsForPermission( namespaceCode, permissionName);
312        if ( roleIds.isEmpty() ) {
313                return Collections.emptyList();
314        }
315        Collection<RoleMembership> roleMembers = roleService.getRoleMembers( roleIds,qualification );
316        List<Assignee> results = new ArrayList<Assignee>();
317        for ( RoleMembership rm : roleMembers ) {
318                        List<DelegateType.Builder> delegateBuilderList = new ArrayList<DelegateType.Builder>();
319                        if (!rm.getDelegates().isEmpty()) {
320                        for (DelegateType delegate : rm.getDelegates()){
321                    delegateBuilderList.add(DelegateType.Builder.create(delegate));
322                        }
323                        }
324                if ( MemberType.PRINCIPAL.equals(rm.getType()) ) {
325                        results.add (Assignee.Builder.create(rm.getMemberId(), null, delegateBuilderList).build());
326                } else if ( MemberType.GROUP.equals(rm.getType()) ) {
327                        results.add (Assignee.Builder.create(null, rm.getMemberId(), delegateBuilderList).build());
328                }
329        }
330
331        return Collections.unmodifiableList(results);
332    }
333
334    @Override
335    public List<Assignee> getPermissionAssigneesByTemplate(String namespaceCode, String permissionTemplateName,
336            Map<String, String> permissionDetails, Map<String, String> qualification) throws RiceIllegalArgumentException {
337        incomingParamCheck(namespaceCode, "namespaceCode");
338        incomingParamCheck(permissionTemplateName, "permissionTemplateName");
339        incomingParamCheck(qualification, "qualification");
340
341        List<String> roleIds = getRoleIdsForPermissionTemplate( namespaceCode, permissionTemplateName, permissionDetails);
342        if ( roleIds.isEmpty() ) {
343                return Collections.emptyList();
344        }
345        Collection<RoleMembership> roleMembers = roleService.getRoleMembers( roleIds,qualification);
346        List<Assignee> results = new ArrayList<Assignee>();
347        for ( RoleMembership rm : roleMembers ) {
348                        List<DelegateType.Builder> delegateBuilderList = new ArrayList<DelegateType.Builder>();
349                        if (!rm.getDelegates().isEmpty()) {
350                        for (DelegateType delegate : rm.getDelegates()){
351                    delegateBuilderList.add(DelegateType.Builder.create(delegate));
352                        }
353                        }
354                if ( MemberType.PRINCIPAL.equals(rm.getType()) ) {
355                        results.add (Assignee.Builder.create(rm.getMemberId(), null, delegateBuilderList).build());
356                } else { // a group membership
357                        results.add (Assignee.Builder.create(null, rm.getMemberId(), delegateBuilderList).build());
358                }
359        }
360        return Collections.unmodifiableList(results);
361    }
362
363    @Override
364    public boolean isPermissionDefined( String namespaceCode, String permissionName ) throws RiceIllegalArgumentException {
365        incomingParamCheck(namespaceCode, "namespaceCode");
366        incomingParamCheck(permissionName, "permissionName");
367
368        // get all the permission objects whose name match that requested
369        List<Permission> permissions = getPermissionsByName(namespaceCode, permissionName);
370        // now, filter the full list by the detail passed
371        return !getMatchingPermissions(permissions, null).isEmpty();
372    }
373
374    @Override
375    public boolean isPermissionDefinedByTemplate(String namespaceCode, String permissionTemplateName,
376            Map<String, String> permissionDetails) throws RiceIllegalArgumentException {
377
378        incomingParamCheck(namespaceCode, "namespaceCode");
379        incomingParamCheck(permissionTemplateName, "permissionTemplateName");
380
381        // get all the permission objects whose name match that requested
382        List<Permission> permissions = getPermissionsByTemplateName(namespaceCode, permissionTemplateName);
383        // now, filter the full list by the detail passed
384        return !getMatchingPermissions(permissions, permissionDetails).isEmpty();
385    }
386
387    @Override
388    public List<String> getRoleIdsForPermission(String namespaceCode, String permissionName) throws RiceIllegalArgumentException {
389        incomingParamCheck(namespaceCode, "namespaceCode");
390        incomingParamCheck(permissionName, "permissionName");
391        // note...this method is cached at the RoleService interface level using an annotation, but it's called quite
392        // a bit internally, so we'll reproduce the caching here using the same key to help optimize
393        String cacheKey =  new StringBuilder("{RoleIds}")
394                .append("namespaceCode=").append(namespaceCode).append("|")
395                .append("name=").append(permissionName).toString();
396        Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
397        if (cachedValue != null && cachedValue.get() instanceof List) {
398            return ((List<String>)cachedValue.get());
399        }
400        // get all the permission objects whose name match that requested
401        List<Permission> permissions = getPermissionsByName(namespaceCode, permissionName);
402        // now, filter the full list by the detail passed
403        List<Permission> applicablePermissions = getMatchingPermissions(permissions, null);
404        List<String> roleIds = getRoleIdsForPermissions(applicablePermissions);
405        cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, roleIds);
406        return roleIds;
407    }
408
409    protected List<String> getRoleIdsForPermissionTemplate(String namespaceCode,
410            String permissionTemplateName,
411            Map<String, String> permissionDetails) {
412        String cacheKey =  new StringBuilder("{getRoleIdsForPermissionTemplate}")
413                .append("namespaceCode=").append(namespaceCode).append("|")
414                .append("permissionTemplateName=").append(permissionTemplateName).append("|")
415                .append("permissionDetails=").append(CacheKeyUtils.mapKey(permissionDetails)).toString();
416        Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
417        if (cachedValue != null && cachedValue.get() instanceof List) {
418            return ((List<String>)cachedValue.get());
419        }
420        // get all the permission objects whose name match that requested
421        List<Permission> permissions = getPermissionsByTemplateName(namespaceCode, permissionTemplateName);
422        // now, filter the full list by the detail passed
423        List<Permission> applicablePermissions = getMatchingPermissions(permissions, permissionDetails);
424        List<String> roleIds = getRoleIdsForPermissions(applicablePermissions);
425        cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, roleIds);
426        return roleIds;
427    }
428
429    // --------------------
430    // Permission Data
431    // --------------------
432    
433    @Override
434    public Permission getPermission(String permissionId) throws RiceIllegalArgumentException {
435        incomingParamCheck(permissionId, "permissionId");
436        PermissionBo impl = getPermissionImpl(permissionId);
437        if (impl != null) {
438                return PermissionBo.to(impl);
439        }
440        return null;
441    }
442    
443    @Override
444    public List<Permission> findPermissionsByTemplate(String namespaceCode, String permissionTemplateName) throws RiceIllegalArgumentException {
445        incomingParamCheck(namespaceCode, "namespaceCode");
446        incomingParamCheck(permissionTemplateName, "permissionTemplateName");
447
448        List<Permission> perms = getPermissionsByTemplateName(namespaceCode, permissionTemplateName);
449        List<Permission> results = new ArrayList<Permission>(perms.size());
450        for (Permission perm : perms) {
451            results.add(perm);
452        }
453        return Collections.unmodifiableList(results);
454    }
455
456        protected PermissionBo getPermissionImpl(String permissionId) throws RiceIllegalArgumentException {
457        incomingParamCheck(permissionId, "permissionId");
458
459        HashMap<String,Object> pk = new HashMap<String,Object>( 1 );
460        pk.put( KimConstants.PrimaryKeyConstants.PERMISSION_ID, permissionId );
461        return businessObjectService.findByPrimaryKey( PermissionBo.class, pk );
462    }
463    
464    protected List<Permission> getPermissionsByTemplateName( String namespaceCode, String permissionTemplateName ){
465        String cacheKey =  new StringBuilder("{getPermissionsByTemplateName}")
466                .append("namespaceCode=").append(namespaceCode).append("|")
467                .append("permissionTemplateName=").append(permissionTemplateName).toString();
468        Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
469        if (cachedValue != null && cachedValue.get() instanceof List) {
470            return ((List<Permission>)cachedValue.get());
471        }
472        HashMap<String,Object> criteria = new HashMap<String,Object>(3);
473        criteria.put("template.namespaceCode", namespaceCode);
474        criteria.put("template.name", permissionTemplateName);
475        criteria.put(KRADPropertyConstants.ACTIVE, "Y");
476        List<Permission> permissions =
477                toPermissions(businessObjectService.findMatching(PermissionBo.class, criteria));
478        cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, permissions);
479        return permissions;
480    }
481
482    protected List<Permission> getPermissionsByName( String namespaceCode, String permissionName ) {
483        String cacheKey =  new StringBuilder("{getPermissionsByName}")
484                .append("namespaceCode=").append(namespaceCode).append("|")
485                .append("permissionName=").append(permissionName).toString();
486        Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
487        if (cachedValue != null && cachedValue.get() instanceof List) {
488            return ((List<Permission>)cachedValue.get());
489        }
490        HashMap<String,Object> criteria = new HashMap<String,Object>(3);
491        criteria.put(KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode);
492        criteria.put(KimConstants.UniqueKeyConstants.PERMISSION_NAME, permissionName);
493        criteria.put(KRADPropertyConstants.ACTIVE, "Y");
494        List<Permission> permissions =
495                toPermissions(businessObjectService.findMatching( PermissionBo.class, criteria ));
496        cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, permissions);
497        return permissions;
498    }
499        
500    @Override
501        public Template getPermissionTemplate(String permissionTemplateId) throws RiceIllegalArgumentException {
502        incomingParamCheck(permissionTemplateId, "permissionTemplateId");
503
504        PermissionTemplateBo impl = businessObjectService.findBySinglePrimaryKey( PermissionTemplateBo.class, permissionTemplateId );
505                if ( impl != null ) {
506                        return PermissionTemplateBo.to(impl);
507                }
508                return null;
509        }
510
511    @Override
512        public Template findPermTemplateByNamespaceCodeAndName(String namespaceCode,
513            String permissionTemplateName) throws RiceIllegalArgumentException {
514                incomingParamCheck(namespaceCode, "namespaceCode");
515        incomingParamCheck(permissionTemplateName, "permissionTemplateName");
516
517        Map<String,String> criteria = new HashMap<String,String>(2);
518                criteria.put( KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode );
519                criteria.put( KimConstants.UniqueKeyConstants.PERMISSION_TEMPLATE_NAME, permissionTemplateName );
520                PermissionTemplateBo impl = businessObjectService.findByPrimaryKey( PermissionTemplateBo.class, criteria );
521                if ( impl != null ) {
522                        return PermissionTemplateBo.to(impl);
523                }
524                return null;
525        }
526
527    @Override
528        public List<Template> getAllTemplates() {
529                if ( allTemplates.isEmpty() ) {
530                        Map<String,String> criteria = new HashMap<String,String>(1);
531                        criteria.put( KRADPropertyConstants.ACTIVE, "Y" );
532                        List<PermissionTemplateBo> impls = (List<PermissionTemplateBo>) businessObjectService.findMatching( PermissionTemplateBo.class, criteria );
533                        List<Template> infos = new ArrayList<Template>( impls.size() );
534                        for ( PermissionTemplateBo impl : impls ) {
535                                infos.add( PermissionTemplateBo.to(impl) );
536                        }
537                        Collections.sort(infos, new Comparator<Template>() {
538                                @Override public int compare(Template tmpl1,
539                                                Template tmpl2) {
540
541                                        int result = tmpl1.getNamespaceCode().compareTo(tmpl2.getNamespaceCode());
542                                        if ( result != 0 ) {
543                                                return result;
544                                        }
545                                        result = tmpl1.getName().compareTo(tmpl2.getName());
546                                        return result;
547                                }
548                        });
549                        allTemplates.addAll(infos);
550                }
551                return Collections.unmodifiableList(allTemplates);
552    }
553
554
555        @Override
556        public Permission createPermission(Permission permission)
557                        throws RiceIllegalArgumentException, RiceIllegalStateException {
558        incomingParamCheck(permission, "permission");
559
560        if (StringUtils.isNotBlank(permission.getId()) && getPermission(permission.getId()) != null) {
561            throw new RiceIllegalStateException("the permission to create already exists: " + permission);
562        }
563        List<PermissionAttributeBo> attrBos = Collections.emptyList();
564        if (permission.getTemplate() != null) {
565            attrBos = KimAttributeDataBo.createFrom(PermissionAttributeBo.class, permission.getAttributes(), permission.getTemplate().getKimTypeId());
566        }
567        PermissionBo bo = PermissionBo.from(permission);
568        if (bo.getTemplate() == null && bo.getTemplateId() != null) {
569            bo.setTemplate(PermissionTemplateBo.from(getPermissionTemplate(bo.getTemplateId())));
570        }
571        bo.setAttributeDetails(attrBos);
572        return PermissionBo.to(businessObjectService.save(bo));
573        }
574
575        @Override
576        public Permission updatePermission(Permission permission)
577                        throws RiceIllegalArgumentException, RiceIllegalStateException {
578        incomingParamCheck(permission, "permission");
579
580        PermissionBo oldPermission = getPermissionImpl(permission.getId());
581        if (StringUtils.isBlank(permission.getId()) || oldPermission == null) {
582            throw new RiceIllegalStateException("the permission does not exist: " + permission);
583        }
584
585        //List<PermissionAttributeBo> attrBos = KimAttributeDataBo.createFrom(PermissionAttributeBo.class, permission.getAttributes(), permission.getTemplate().getKimTypeId());
586
587        List<PermissionAttributeBo> oldAttrBos = oldPermission.getAttributeDetails();
588        //put old attributes in map for easier updating
589        Map<String, PermissionAttributeBo> oldAttrMap = new HashMap<String, PermissionAttributeBo>();
590        for (PermissionAttributeBo oldAttr : oldAttrBos) {
591            oldAttrMap.put(oldAttr.getKimAttribute().getAttributeName(), oldAttr);
592        }
593        List<PermissionAttributeBo> newAttrBos = new ArrayList<PermissionAttributeBo>();
594        for (String key : permission.getAttributes().keySet()) {
595            if (oldAttrMap.containsKey(key)) {
596                PermissionAttributeBo updatedAttr = oldAttrMap.get(key);
597                updatedAttr.setAttributeValue(permission.getAttributes().get(key));
598                newAttrBos.add(updatedAttr);
599            } else { //new attribute
600                newAttrBos.addAll(KimAttributeDataBo.createFrom(PermissionAttributeBo.class, Collections.singletonMap(key, permission.getAttributes().get(key)), permission.getTemplate().getKimTypeId()));
601            }
602        }
603        PermissionBo bo = PermissionBo.from(permission);
604        if (CollectionUtils.isNotEmpty(newAttrBos)) {
605            if(null!= bo.getAttributeDetails())  {
606                bo.getAttributeDetails().clear();
607            }
608            bo.setAttributeDetails(newAttrBos);
609        }
610        if (bo.getTemplate() == null && bo.getTemplateId() != null) {
611            bo.setTemplate(PermissionTemplateBo.from(getPermissionTemplate(bo.getTemplateId())));
612        }
613
614        return PermissionBo.to(businessObjectService.save(bo));         
615        }
616        
617    @Override
618    public Permission findPermByNamespaceCodeAndName(String namespaceCode, String permissionName)
619            throws RiceIllegalArgumentException {
620        incomingParamCheck(namespaceCode, "namespaceCode");
621        incomingParamCheck(permissionName, "permissionName");
622
623        PermissionBo permissionBo = getPermissionBoByName(namespaceCode, permissionName);
624        if (permissionBo != null) {
625            return PermissionBo.to(permissionBo);
626        }
627        return null;
628    }
629    
630    protected PermissionBo getPermissionBoByName(String namespaceCode, String permissionName) {
631        if (StringUtils.isBlank(namespaceCode)
632                || StringUtils.isBlank(permissionName)) {
633            return null;
634        }
635        Map<String, String> criteria = new HashMap<String, String>();
636        criteria.put(KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode);
637        criteria.put(KimConstants.UniqueKeyConstants.NAME, permissionName);
638        criteria.put(KRADPropertyConstants.ACTIVE, "Y");
639        // while this is not actually the primary key - there will be at most one row with these criteria
640        return businessObjectService.findByPrimaryKey(PermissionBo.class, criteria);
641    }
642
643    @Override
644    public PermissionQueryResults findPermissions(final QueryByCriteria queryByCriteria)
645            throws RiceIllegalArgumentException {
646        incomingParamCheck(queryByCriteria, "queryByCriteria");
647
648        LookupCustomizer.Builder<PermissionBo> lc = LookupCustomizer.Builder.create();
649        lc.setPredicateTransform(AttributeTransform.getInstance());
650
651        GenericQueryResults<PermissionBo> results = criteriaLookupService.lookup(PermissionBo.class, queryByCriteria, lc.build());
652
653        PermissionQueryResults.Builder builder = PermissionQueryResults.Builder.create();
654        builder.setMoreResultsAvailable(results.isMoreResultsAvailable());
655        builder.setTotalRowCount(results.getTotalRowCount());
656
657        final List<Permission.Builder> ims = new ArrayList<Permission.Builder>();
658        for (PermissionBo bo : results.getResults()) {
659            ims.add(Permission.Builder.create(bo));
660        }
661
662        builder.setResults(ims);
663        return builder.build();
664    }
665
666    @Override
667    public TemplateQueryResults findPermissionTemplates(final QueryByCriteria queryByCriteria)
668            throws RiceIllegalArgumentException {
669        incomingParamCheck(queryByCriteria, "queryByCriteria");
670
671        GenericQueryResults<PermissionTemplateBo> results = criteriaLookupService.lookup(PermissionTemplateBo.class, queryByCriteria);
672
673        TemplateQueryResults.Builder builder = TemplateQueryResults.Builder.create();
674        builder.setMoreResultsAvailable(results.isMoreResultsAvailable());
675        builder.setTotalRowCount(results.getTotalRowCount());
676
677        final List<Template.Builder> ims = new ArrayList<Template.Builder>();
678        for (PermissionTemplateBo bo : results.getResults()) {
679            ims.add(Template.Builder.create(bo));
680        }
681
682        builder.setResults(ims);
683        return builder.build();
684    }
685
686    private List<String> getRoleIdsForPermissions( Collection<Permission> permissions ) {
687        if (CollectionUtils.isEmpty(permissions)) {
688            return Collections.emptyList();
689        }
690        List<String> ids = new ArrayList<String>();
691        for (Permission p : permissions) {
692            ids.add(p.getId());
693        }
694
695        return getRoleIdsForPermissionIds(ids);
696    }
697
698    private List<String> getRoleIdsForPermissionIds(Collection<String> permissionIds) {
699        if (CollectionUtils.isEmpty(permissionIds)) {
700            return Collections.emptyList();
701        }
702        String cacheKey =  new StringBuilder("{getRoleIdsForPermissionIds}")
703                .append("permissionIds=").append(CacheKeyUtils.key(permissionIds)).toString();
704        Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
705        if (cachedValue != null && cachedValue.get() instanceof List) {
706            return ((List<String>)cachedValue.get());
707        }
708        QueryByCriteria query = QueryByCriteria.Builder.fromPredicates(equal("active", "true"), in("permissionId", permissionIds.toArray(new String[]{})));
709        GenericQueryResults<RolePermissionBo> results = criteriaLookupService.lookup(RolePermissionBo.class, query);
710        List<String> roleIds = new ArrayList<String>();
711        for (RolePermissionBo bo : results.getResults()) {
712            roleIds.add(bo.getRoleId());
713        }
714        roleIds = Collections.unmodifiableList(roleIds);
715        cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, roleIds);
716        return roleIds;
717    }
718        
719        /**
720     * Sets the kimTypeInfoService attribute value.
721     *
722     * @param kimTypeInfoService The kimTypeInfoService to set.
723     */
724        public void setKimTypeInfoService(KimTypeInfoService kimTypeInfoService) {
725                this.kimTypeInfoService = kimTypeInfoService;
726        }
727        
728        /**
729     * Sets the defaultPermissionTypeService attribute value.
730     *
731     * @param defaultPermissionTypeService The defaultPermissionTypeService to set.
732     */
733        public void setDefaultPermissionTypeService(PermissionTypeService defaultPermissionTypeService) {
734        this.defaultPermissionTypeService = defaultPermissionTypeService;
735        }
736        
737        /**
738     * Sets the roleService attribute value.
739     *
740     * @param roleService The roleService to set.
741     */
742        public void setRoleService(RoleService roleService) {
743                this.roleService = roleService;
744        }
745
746    /**
747     * Sets the businessObjectService attribute value.
748     *
749     * @param businessObjectService The businessObjectService to set.
750     */
751    public void setBusinessObjectService(final BusinessObjectService businessObjectService) {
752        this.businessObjectService = businessObjectService;
753    }
754
755    /**
756     * Sets the criteriaLookupService attribute value.
757     *
758     * @param criteriaLookupService The criteriaLookupService to set.
759     */
760    public void setCriteriaLookupService(final CriteriaLookupService criteriaLookupService) {
761        this.criteriaLookupService = criteriaLookupService;
762    }
763
764    /**
765     * Sets the cache manager which this service implementation can for internal caching.
766     * Calling this setter is optional, though the value passed to it must not be null.
767     *
768     * @param cacheManager the cache manager to use for internal caching, must not be null
769     * @throws IllegalArgumentException if a null cache manager is passed
770     */
771    public void setCacheManager(final CacheManager cacheManager) {
772        if (cacheManager == null) {
773            throw new IllegalArgumentException("cacheManager must not be null");
774        }
775        this.cacheManager = cacheManager;
776    }
777
778    private List<Permission> toPermissions(Collection<PermissionBo> permissionBos) {
779        if (CollectionUtils.isEmpty(permissionBos)) {
780            return new ArrayList<Permission>();
781        }
782        List<Permission> permissions = new ArrayList<Permission>(permissionBos.size());
783        for (PermissionBo permissionBo : permissionBos) {
784            permissions.add(PermissionBo.to(permissionBo));
785        }
786        return permissions;
787    }
788    
789    protected void logAuthorizationCheck(String checkType, String principalId, String namespaceCode, String permissionName, Map<String, String> qualification ) {
790        StringBuilder sb = new StringBuilder();
791        sb.append(  '\n' );
792        sb.append( "Is AuthZ for " ).append( checkType ).append( ": " ).append( namespaceCode ).append( "/" ).append( permissionName ).append( '\n' );
793        sb.append( "             Principal:  " ).append( principalId );
794        if ( principalId != null ) {
795            Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(principalId);
796            if ( principal != null ) {
797                sb.append( " (" ).append( principal.getPrincipalName() ).append( ')' );
798            }
799        }
800        sb.append( '\n' );
801        sb.append( "             Qualifiers:\n" );
802        if ( qualification != null && !qualification.isEmpty() ) {
803            sb.append( qualification );
804        } else {
805            sb.append( "                         [null]\n" );
806        }
807        if (LOG.isTraceEnabled()) {
808            LOG.trace( sb.append(ExceptionUtils.getStackTrace(new Throwable())));
809        } else {
810            LOG.debug(sb.toString());
811        }
812    }
813    
814    protected void logAuthorizationCheckByTemplate(String checkType, String principalId, String namespaceCode, String permissionName, 
815                                                   Map<String, String> permissionDetails, Map<String, String> qualification ) {
816        StringBuilder sb = new StringBuilder();
817        sb.append(  '\n' );
818        sb.append( "Is AuthZ for " ).append( checkType ).append( ": " ).append( namespaceCode ).append( "/" ).append( permissionName ).append( '\n' );
819        sb.append( "             Principal:  " ).append( principalId );
820        if ( principalId != null ) {
821            Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(principalId);
822            if ( principal != null ) {
823                sb.append( " (" ).append( principal.getPrincipalName() ).append( ')' );
824            }
825        }
826        sb.append( '\n' );
827        sb.append( "             Details:\n" );
828        if ( permissionDetails != null ) {
829            sb.append( permissionDetails );
830        } else {
831            sb.append( "                         [null]\n" );
832        }
833        sb.append( "             Qualifiers:\n" );
834        if ( qualification != null && !qualification.isEmpty() ) {
835            sb.append( qualification );
836        } else {
837            sb.append( "                         [null]\n" );
838        }
839        if (LOG.isTraceEnabled()) {
840            LOG.trace( sb.append(ExceptionUtils.getStackTrace(new Throwable())));
841        } else {
842            LOG.debug(sb.toString());
843        }
844    }
845    
846    private void incomingParamCheck(Object object, String name) {
847        if (object == null) {
848            throw new RiceIllegalArgumentException(name + " was null");
849        } else if (object instanceof String
850                && StringUtils.isBlank((String) object)) {
851            throw new RiceIllegalArgumentException(name + " was blank");
852        }
853    }
854
855
856}