001package org.apache.archiva.redback.role.template;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.archiva.redback.rbac.Operation;
023import org.apache.archiva.redback.rbac.Permission;
024import org.apache.archiva.redback.rbac.RbacManagerException;
025import org.apache.archiva.redback.rbac.Resource;
026import org.apache.archiva.redback.rbac.Role;
027import org.apache.archiva.redback.rbac.RBACManager;
028import org.apache.archiva.redback.role.PermanentRoleDeletionInvalid;
029import org.apache.archiva.redback.role.RoleExistsException;
030import org.apache.archiva.redback.role.RoleManagerException;
031import org.apache.archiva.redback.role.RoleNotFoundException;
032import org.apache.archiva.redback.role.model.ModelApplication;
033import org.apache.archiva.redback.role.model.ModelOperation;
034import org.apache.archiva.redback.role.model.ModelPermission;
035import org.apache.archiva.redback.role.model.ModelResource;
036import org.apache.archiva.redback.role.model.ModelRole;
037import org.apache.archiva.redback.role.model.ModelTemplate;
038import org.apache.archiva.redback.role.model.RedbackRoleModel;
039import org.apache.archiva.redback.role.util.RoleModelUtils;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042import org.springframework.stereotype.Service;
043
044import javax.inject.Inject;
045import javax.inject.Named;
046import java.util.ArrayList;
047import java.util.Collections;
048import java.util.Iterator;
049import java.util.List;
050
051/**
052 * DefaultRoleTemplateProcessor: inserts the components of a template into the rbac manager
053 *
054 * @author: Jesse McConnell
055 */
056@Service("roleTemplateProcessor")
057public class DefaultRoleTemplateProcessor
058    implements RoleTemplateProcessor
059{
060    private Logger log = LoggerFactory.getLogger( DefaultRoleTemplateProcessor.class );
061
062    @Inject
063    @Named(value = "rbacManager#default")
064    private RBACManager rbacManager;
065
066    @Override
067    @SuppressWarnings("unchecked")
068    public String create( final RedbackRoleModel model, final String templateId, final String resource )
069        throws RoleManagerException
070    {
071        for ( ModelApplication application : model.getApplications() )
072        {
073            for ( ModelTemplate template : application.getTemplates() )
074            {
075                if ( templateId.equals( template.getId() ) )
076                {
077                    // resource can be special
078                    processResource( template, resource );
079
080                    // templates are roles that have yet to be paired with a resource for creation
081                    return processTemplate( model, template, resource );
082
083                }
084            }
085        }
086
087        throw new RoleNotFoundException( "unknown template '" + templateId + "'" );
088    }
089
090    @Override
091    @SuppressWarnings("unchecked")
092    public void remove( RedbackRoleModel model, String templateId, String resource )
093        throws RoleManagerException
094    {
095        for ( ModelApplication application : model.getApplications() )
096        {
097            for ( ModelTemplate template : application.getTemplates() )
098            {
099                if ( templateId.equals( template.getId() ) )
100                {
101                    removeTemplatedRole( model, template, resource );
102                    return;
103                }
104            }
105        }
106
107        throw new RoleManagerException( "unknown template '" + templateId + "'" );
108    }
109
110    private void removeTemplatedRole( RedbackRoleModel model, ModelTemplate template, String resource )
111        throws RoleManagerException
112    {
113        String roleId = getRoleId( template.getId( ), resource );
114
115        try
116        {
117            Role role = rbacManager.getRoleById( roleId );
118
119            if ( !role.isPermanent() )
120            {
121                // remove the role
122                rbacManager.removeRole( role );
123
124                // remove the permissions
125                // todo, do this in a better way too, permissions can be shared across multiple roles and that could blow chunks here.
126                //for ( Iterator i = template.getPermissions().iterator(); i.hasNext(); )
127                //{
128                //    ModelPermission permission = (ModelPermission) i.next();
129                //    if ( !permission.isPermanent() )
130                //    {                                                                        
131                //            rbacManager.removePermission( permission.getName() + template.getDelimiter()
132                //                       + resolvePermissionResource( model, permission, resolvePermissionResource( model, permission, resource ) ) );                     
133                //   }
134                //}
135
136                // check if we want to remove the resources
137                Resource rbacResource = rbacManager.getResource( resource );
138
139                //if ( !rbacResource.isPermanent() )
140                //{
141                    //todo we need a better way of finding if a resource is unused anymore...probably a cleaning process in the db or something
142                    //rbacManager.removeResource( rbacResource );
143                //}
144
145                // todo find dangling child role references and smoke
146            }
147            else
148            {
149                throw new PermanentRoleDeletionInvalid( "Unable to remove role, it is flagged permanent: "+roleId );
150            }
151        }
152        catch ( RbacManagerException e )
153        {
154            throw new RoleManagerException( "Unable to remove templated role: " + roleId, e );
155        }
156        //catch ( RoleTemplateProcessorException e )
157        //{
158        //    throw new RoleManagerException( "unable to remove templated role, error resolving resource: Role:" + roleName + " Resource: " + resource, e );
159        //}
160    }
161
162    private void processResource( ModelTemplate template, String resource )
163        throws RoleManagerException
164    {
165        if ( !rbacManager.resourceExists( resource ) )
166        {
167            try
168            {
169                Resource res = rbacManager.createResource( resource );
170                res.setPermanent( template.isPermanentResource() );
171                rbacManager.saveResource( res );
172            }
173            catch ( RbacManagerException e )
174            {
175                throw new RoleManagerException( "error creating resource '" + resource + "'", e );
176            }
177        }
178    }
179
180    @Override
181    public String getRoleId( String templateId, String resource) {
182        return RoleModelUtils.getRoleId( templateId, resource );
183    }
184
185    @SuppressWarnings("unchecked")
186    private String processTemplate( RedbackRoleModel model, ModelTemplate template, String resource )
187        throws RoleManagerException
188    {
189        final String templateName = template.getNamePrefix() + template.getDelimiter() + resource;
190        final String roleId = getRoleId( template.getId( ), resource );
191
192        List<Permission> permissions = processPermissions( model, template, resource );
193
194        boolean roleExists = false;
195
196        try
197        {
198            roleExists = rbacManager.roleExists( templateName );
199        }
200        catch ( RbacManagerException e )
201        {
202            throw new RoleExistsException( e.getMessage(), e );
203        }
204
205        if ( !roleExists )
206        {
207            try
208            {
209                Role role = rbacManager.createRole( templateName );
210                role.setId( roleId );
211                role.setModelId( template.getId() );
212                role.setResource( resource );
213                role.setTemplateInstance( true );
214                role.setDescription( template.getDescription() );
215                role.setPermanent( template.isPermanent() );
216                role.setAssignable( template.isAssignable() );
217
218                // add any permissions associated with this role
219                for ( Iterator<Permission> j = permissions.iterator(); j.hasNext(); )
220                {
221                    Permission permission = j.next();
222
223                    role.addPermission( permission );
224                }
225
226                // add child roles to this role
227                if ( template.getChildRoles() != null )
228                {
229                    for ( String childRoleId : template.getChildRoles() )
230                    {
231                        ModelRole childRoleProfile = RoleModelUtils.getModelRole( model, childRoleId );
232                        role.addChildRoleName( childRoleProfile.getName() );
233                        role.addChildRoleId( childRoleProfile.getId() );
234                    }
235                }
236
237                // add child templates to this role, be nice and make them if they don't exist
238                if ( template.getChildTemplates() != null )
239                {
240                    for ( String childTemplateId : template.getChildTemplates() )
241                    {
242                        ModelTemplate childModelTemplate = RoleModelUtils.getModelTemplate( model, childTemplateId );
243
244                        if ( childModelTemplate == null )
245                        {
246                            throw new RoleManagerException(
247                                "error obtaining child template from model: template " + templateName
248                                    + " # child template: " + childTemplateId );
249                        }
250
251                        String childRoleName =
252                            childModelTemplate.getNamePrefix() + childModelTemplate.getDelimiter() + resource;
253
254                        // check if the role exists, if it does then add it as a child, otherwise make it and add it
255                        // this should be safe since validation should protect us from template cycles
256                        if ( rbacManager.roleExists( childRoleName ) )
257                        {
258                            role.addChildRoleName( childRoleName );
259                            role.addChildRoleId( getRoleId( childTemplateId, resource ) );
260                        }
261                        else
262                        {
263                            processTemplate( model, childModelTemplate, resource );
264
265                            role.addChildRoleName( childRoleName );
266                            role.addChildRoleId( getRoleId( childTemplateId, resource ) );
267                        }
268                    }
269                }
270
271                // this role needs to be saved since it now needs to be added as a child role by 
272                // another role
273                if ( !rbacManager.roleExists( role.getName() ) )
274                {
275                    role = rbacManager.saveRole( role );
276                }
277
278                // add link from parent roles to this new role
279                if ( template.getParentRoles() != null )
280                {
281                    for ( String parentRoleId : template.getParentRoles() )
282                    {
283                        ModelRole parentModelRole = RoleModelUtils.getModelRole( model, parentRoleId );
284                        Role parentRole = rbacManager.getRole( parentModelRole.getName() );
285                        parentRole.addChildRole( role );
286                        rbacManager.saveRole( parentRole );
287                    }
288                }
289
290                // add child templates to this role, be nice and make them if they don't exist
291                if ( template.getParentTemplates() != null )
292                {
293                    for ( String parentTemplateId : template.getParentTemplates() )
294                    {
295                        ModelTemplate parentModelTemplate = RoleModelUtils.getModelTemplate( model, parentTemplateId );
296
297                        if ( parentModelTemplate == null )
298                        {
299                            throw new RoleManagerException(
300                                "error obtaining parent template from model: template " + templateName
301                                    + " # child template: " + parentTemplateId );
302                        }
303
304                        String parentRoleName =
305                            parentModelTemplate.getNamePrefix() + parentModelTemplate.getDelimiter() + resource;
306
307                        // check if the role exists, if it does then add it as a child, otherwise make it and add it
308                        // this should be safe since validation should protect us from template cycles
309                        if ( rbacManager.roleExists( parentRoleName ) )
310                        {
311                            Role parentRole = rbacManager.getRole( parentRoleName );
312
313                            parentRole.addChildRole( role );
314                            rbacManager.saveRole( parentRole );
315                        }
316                        else
317                        {
318                            processTemplate( model, parentModelTemplate, resource );
319
320                            Role parentRole = rbacManager.getRole( parentRoleName );
321
322                            parentRole.addChildRole( role );
323                            rbacManager.saveRole( parentRole );
324                        }
325                    }
326                }
327
328            }
329            catch ( RbacManagerException e )
330            {
331                throw new RoleManagerException( "error creating role '" + templateName + "'", e );
332            }
333        }
334        else
335        {
336            try
337            {
338                Role role = rbacManager.getRole( templateName );
339
340                boolean changed = false;
341                for ( Permission permission : permissions )
342                {
343                    if ( !role.getPermissions().contains( permission ) )
344                    {
345                        log.info( "Adding new permission '{}' to role '{}'",
346                                  permission.getName(), role.getName() );
347                        role.addPermission( permission );
348                        changed = true;
349                    }
350                }
351
352                // Copy list to avoid concurrent modifications
353                List<Permission> oldPermissions = new ArrayList<Permission>( role.getPermissions() );
354                for ( Permission permission : oldPermissions )
355                {
356                    if ( !permissions.contains( permission ) )
357                    {
358                        log.info( "Removing old permission '{}' from role '{}'", permission.getName(), role.getName() );
359                        role.removePermission( permission );
360                        changed = true;
361                    }
362                }
363                if ( changed )
364                {
365                    rbacManager.saveRole( role );
366                }
367            }
368            catch ( RbacManagerException e )
369            {
370                throw new RoleManagerException( "error updating role '" + templateName + "'", e );
371            }
372        }
373        return roleId;
374    }
375
376    @SuppressWarnings("unchecked")
377    private List<Permission> processPermissions( RedbackRoleModel model, ModelTemplate template, String resource )
378        throws RoleManagerException
379    {
380
381        if ( template.getPermissions() != null )
382        {
383            // copy list to avoid concurrent modifications
384            List<ModelPermission> templatePermissions = new ArrayList<ModelPermission>( template.getPermissions() );
385            List<Permission> rbacPermissions = new ArrayList<Permission>( templatePermissions.size() );
386            for ( ModelPermission profilePermission : templatePermissions )
387            {
388                try
389                {
390                    String permissionName =
391                        profilePermission.getName() + template.getDelimiter() + resolvePermissionResource( model,
392                                                                                                           profilePermission,
393                                                                                                           resource );
394
395                    if ( !rbacManager.permissionExists( permissionName ) )
396                    {
397
398                        Permission permission = rbacManager.createPermission( permissionName );
399
400                        ModelOperation modelOperation =
401                            RoleModelUtils.getModelOperation( model, profilePermission.getOperation() );
402                        Operation rbacOperation = rbacManager.getOperation( modelOperation.getName() );
403
404                        String permissionResource = resolvePermissionResource( model, profilePermission, resource );
405
406                        Resource rbacResource = rbacManager.getResource( permissionResource );
407
408                        permission.setOperation( rbacOperation );
409                        permission.setResource( rbacResource );
410                        permission.setPermanent( profilePermission.isPermanent() );
411                        permission.setDescription( profilePermission.getDescription() );
412
413                        permission = rbacManager.savePermission( permission );
414
415                        rbacPermissions.add( permission );
416
417                    }
418                    else
419                    {
420
421                        rbacPermissions.add( rbacManager.getPermission( permissionName ) );
422
423                    }
424                }
425                catch ( RbacManagerException e )
426                {
427                    throw new RoleManagerException( "unable to generate templated role: " + e.getMessage(), e );
428                }
429                catch ( RoleTemplateProcessorException e )
430                {
431                    throw new RoleManagerException( "unable to resolve resource: " + resource, e );
432                }
433            }
434            return rbacPermissions;
435        }
436
437        return Collections.emptyList();
438    }
439
440    private String resolvePermissionResource( RedbackRoleModel model, ModelPermission permission, String resource )
441        throws RoleTemplateProcessorException
442    {
443        String permissionResource = permission.getResource();
444
445        // if permission's resource is ${resource}, return the resource passed in
446        if ( permissionResource.startsWith( "${" ) )
447        {
448            String tempStr = permissionResource.substring( 2, permissionResource.indexOf( '}' ) );
449
450            if ( "resource".equals( tempStr ) )
451            {
452                return resource;
453            }
454        }
455
456        // check if the resource resolves to declared operation
457        String declaredResource = resolveResource( model, permission.getResource() );
458        if ( declaredResource != null )
459        {
460            return declaredResource;
461        }
462        else
463        {
464            // either niether of the above apply, then its the resource.
465            return resource;
466        }
467    }
468
469    private String resolveResource( RedbackRoleModel model, String resource )
470        throws RoleTemplateProcessorException
471    {
472        ModelResource resolvedResource = RoleModelUtils.getModelResource( model, resource );
473
474        if ( resolvedResource != null )
475        {
476            return resolvedResource.getName();
477        }
478        else
479        {
480            return null;
481        }
482    }
483
484    public RBACManager getRbacManager()
485    {
486        return rbacManager;
487    }
488
489    public void setRbacManager( RBACManager rbacManager )
490    {
491        this.rbacManager = rbacManager;
492    }
493}