001package org.apache.archiva.redback.role.validator;
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.components.graph.base.SimpleGraph;
023import org.apache.archiva.components.graph.base.SimpleNode;
024import org.apache.archiva.components.graph.util.Traversal;
025import org.apache.archiva.redback.role.RoleManagerException;
026import org.apache.archiva.redback.role.model.*;
027import org.apache.archiva.redback.role.util.RoleModelUtils;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030import org.springframework.stereotype.Service;
031
032import java.util.ArrayList;
033import java.util.List;
034
035/**
036 * DefaultRoleModelValidator: validates completeness of the model
037 *
038 * @author: Jesse McConnell
039 */
040@Service("roleModelValidator")
041public class DefaultRoleModelValidator
042        implements RoleModelValidator {
043
044    private static final Logger log = LoggerFactory.getLogger(DefaultRoleModelValidator.class);
045
046    private List<String> validationErrors;
047
048    public boolean validate(RedbackRoleModel model)
049            throws RoleManagerException {
050        validationErrors = null;
051
052        validateRequiredStructure(model);
053        validateResourceClosure(model);
054        validateOperationClosure(model);
055        validateChildRoleClosure(model);
056        validateParentRoleClosure(model);
057        validateTemplateClosure(model);
058        validateNoRoleCycles(model);
059        validateNoTemplateCycles(model);
060
061        if (validationErrors == null) {
062            return true;
063        } else {
064            return false;
065        }
066    }
067
068    public List<String> getValidationErrors() {
069        return validationErrors;
070    }
071
072    private void addValidationError(String error) {
073        if (validationErrors == null) {
074            validationErrors = new ArrayList<String>(0);
075        }
076
077        validationErrors.add(error);
078    }
079
080    /**
081     * FIXME this should be taken care of by <required/> in modello, figure out why its not
082     * in the meantime, implement the basics
083     *
084     * @param model
085     */
086    @SuppressWarnings("unchecked")
087    private void validateRequiredStructure(RedbackRoleModel model) {
088        // validate model has name
089
090        for (ModelApplication application : model.getApplications()) {
091            if (application.getId() == null) {
092                addValidationError("model is missing application name");
093            }
094
095            // validate model has version
096            if (application.getVersion() == null) {
097                addValidationError(application.getId() + " is missing version");
098            }
099
100            // validate resource bits
101            for (ModelResource resource : application.getResources()) {
102                if (resource.getName() == null) {
103                    addValidationError(resource.toString() + " missing name");
104                }
105
106                if (resource.getId() == null) {
107                    addValidationError(resource.toString() + " missing id");
108                }
109            }
110
111            // validate the operations
112            for (ModelOperation operation : application.getOperations()) {
113                if (operation.getName() == null) {
114                    addValidationError(operation.toString() + " missing name");
115                }
116
117                if (operation.getId() == null) {
118                    addValidationError(operation.toString() + " missing id");
119                }
120            }
121
122            for (ModelRole role : application.getRoles()) {
123                if (role.getId() == null) {
124                    addValidationError(role.toString() + " missing id");
125                }
126
127                if (role.getName() == null) {
128                    addValidationError(role.toString() + " missing name");
129                }
130
131                if (role.getPermissions() != null) {
132                    for (ModelPermission permission : role.getPermissions()) {
133                        if (permission.getName() == null) {
134                            addValidationError(permission.toString() + " missing name");
135                        }
136
137                        if (permission.getId() == null) {
138                            addValidationError(permission.toString() + " missing id");
139                        }
140
141                        if (permission.getOperation() == null) {
142                            addValidationError(permission.toString() + " missing operations");
143                        }
144
145                        if (permission.getResource() == null) {
146                            addValidationError(permission.toString() + " missing resource");
147                        }
148                    }
149                }
150            }
151
152            for (ModelTemplate template : application.getTemplates()) {
153                if (template.getId() == null) {
154                    addValidationError(template.toString() + " missing id");
155                }
156
157                if (template.getNamePrefix() == null) {
158                    addValidationError(template.toString() + " missing name prefix");
159                }
160
161                if (template.getPermissions() != null) {
162                    for (ModelPermission permission : template.getPermissions()) {
163                        if (permission.getName() == null) {
164                            addValidationError(permission.toString() + " missing name");
165                        }
166
167                        if (permission.getId() == null) {
168                            addValidationError(permission.toString() + " missing id");
169                        }
170
171                        if (permission.getOperation() == null) {
172                            addValidationError(permission.toString() + " missing operations");
173                        }
174
175                        if (permission.getResource() == null) {
176                            addValidationError(permission.toString() + " missing resource");
177                        }
178                    }
179                }
180            }
181        }
182    }
183
184    /**
185     * validate all operations in all declared permissions exist as declared in the operations section
186     *
187     * @param model
188     */
189    private void validateOperationClosure(RedbackRoleModel model) {
190        List<String> operationIdList = RoleModelUtils.getOperationIdList(model);
191
192        // check the operations in role permissions
193        for (ModelApplication application : model.getApplications()) {
194            for (ModelRole role : application.getRoles()) {
195                if (role.getPermissions() != null) {
196                    for (ModelPermission permission : role.getPermissions()) {
197                        if (!operationIdList.contains(permission.getOperation())) {
198                            addValidationError("missing operation: " + permission.getOperation() + " in permission "
199                                    + permission.getId());
200                        }
201                    }
202                }
203            }
204
205            // check the operations in template permissions
206            for (ModelTemplate template : application.getTemplates()) {
207                if (template.getPermissions() != null) {
208                    for (ModelPermission permission : template.getPermissions()) {
209                        if (!operationIdList.contains(permission.getOperation())) {
210                            addValidationError("missing operation: " + permission.getOperation() + " in permission "
211                                    + permission.getId());
212                        }
213                    }
214                }
215            }
216        }
217    }
218
219    private void validateResourceClosure(RedbackRoleModel model) {
220        List<String> resourceIdList = RoleModelUtils.getResourceIdList(model);
221        for (ModelApplication application : model.getApplications()) {
222            for (ModelRole role : application.getRoles()) {
223                if (role.getPermissions() != null) {
224                    for (ModelPermission permission : role.getPermissions()) {
225                        if (!resourceIdList.contains(permission.getResource())) {
226                            addValidationError("missing operation: " + permission.getResource() + " in permission "
227                                    + permission.getId());
228                        }
229                    }
230                }
231            }
232        }
233    }
234
235    private void validateChildRoleClosure(RedbackRoleModel model) {
236        List<String> roleIdList = RoleModelUtils.getRoleIdList(model);
237        for (ModelApplication application : model.getApplications()) {
238            for (ModelRole role : application.getRoles()) {
239                if (role.getChildRoles() != null) {
240                    for (String childRoleId : role.getChildRoles()) {
241                        if (!roleIdList.contains(childRoleId)) {
242                            addValidationError(
243                                    "missing role id: " + childRoleId + " in child roles of role " + role.getId());
244                        }
245                    }
246                }
247            }
248
249            for (ModelTemplate template : application.getTemplates()) {
250                if (template.getChildRoles() != null) {
251                    for (String childRoleId : template.getChildRoles()) {
252                        if (!roleIdList.contains(childRoleId)) {
253                            addValidationError(
254                                    "missing role id: " + childRoleId + " in child roles of template " + template.getId());
255                        }
256                    }
257                }
258            }
259        }
260    }
261
262    @SuppressWarnings("unchecked")
263    private void validateParentRoleClosure(RedbackRoleModel model) {
264        List roleIdList = RoleModelUtils.getRoleIdList(model);
265
266        for (ModelApplication application : model.getApplications()) {
267            for (ModelRole role : application.getRoles()) {
268                if (role.getParentRoles() != null) {
269                    for (String parentRoleId : role.getParentRoles()) {
270                        if (!roleIdList.contains(parentRoleId)) {
271                            addValidationError(
272                                    "missing role id: " + parentRoleId + " in parent roles of role " + role.getId());
273                        }
274                    }
275                }
276            }
277
278            for (ModelTemplate template : application.getTemplates()) {
279                if (template.getParentRoles() != null) {
280                    for (String parentRoleId : template.getParentRoles()) {
281                        if (!roleIdList.contains(parentRoleId)) {
282                            addValidationError("missing role id: " + parentRoleId + " in parent roles of template "
283                                    + template.getId());
284                        }
285                    }
286                }
287            }
288        }
289    }
290
291    private void validateTemplateClosure(RedbackRoleModel model) {
292        List templateIdList = RoleModelUtils.getTemplateIdList(model);
293
294        // template name prefix must be unique
295        List<String> templateNamePrefixList = new ArrayList<String>();
296
297        for (ModelApplication application : model.getApplications()) {
298            for (ModelTemplate template : application.getTemplates()) {
299                if (template.getParentTemplates() != null) {
300                    for (String parentTemplateId : template.getParentTemplates()) {
301                        if (!templateIdList.contains(parentTemplateId)) {
302                            addValidationError(
303                                    "missing template id: " + parentTemplateId + " in parent templates of template "
304                                            + template.getId());
305                        }
306                    }
307                }
308
309                if (template.getChildTemplates() != null) {
310                    for (String childTemplateId : template.getChildTemplates()) {
311                        if (!templateIdList.contains(childTemplateId)) {
312                            addValidationError(
313                                    "missing template id: " + childTemplateId + " in child templates of template "
314                                            + template.getId());
315                        }
316                    }
317                }
318
319                if (!templateNamePrefixList.contains(template.getNamePrefix())) {
320                    templateNamePrefixList.add(template.getNamePrefix());
321                } else {
322                    addValidationError("duplicate name prefix detected: " + template.getNamePrefix());
323                }
324            }
325        }
326    }
327
328    /**
329     * We are not allowed to have cycles between roles, this method is to detect and raise a red flag when that happens.
330     *
331     * @param model
332     */
333    private void validateNoRoleCycles(RedbackRoleModel model) {
334        log.debug("Validating cycles in role model");
335        SimpleGraph graph = RoleModelUtils.generateRoleGraph(model);
336        SimpleNode rootNode = graph.getNode(RoleModelUtils.ROOT);
337        SimpleNode n;
338        if ((n = Traversal.findFirstCycleNode(rootNode))!=null) {
339            log.debug("Adding template cycle validation error for node {}", n.getId());
340            addValidationError("Cycle detected at "+n.getId());
341        }
342    }
343
344    /**
345     * We are not allowed to have cycles between template either, this method is to detect and
346     * raise a red flag when that happens.  Templates are a bit more complex since they have both
347     * child and parent roles, as well as runtime parent and child templates
348     * <p>
349     * the id should be sufficient to test cycles here even though in runtime the id's do not need to be
350     * unique since it is the binding of a namePrefix and a resource that makes them unique
351     *
352     * @param model
353     */
354    private void validateNoTemplateCycles(RedbackRoleModel model) {
355        log.debug("Validating cycles in role template model ");
356        SimpleGraph graph = RoleModelUtils.generateTemplateGraph(model);
357        SimpleNode rootNode = graph.getNode(RoleModelUtils.ROOT);
358        SimpleNode n;
359        if ((n = Traversal.findFirstCycleNode(rootNode)) != null) {
360            log.debug("Adding template cycle validation error for node {}", n.getId());
361            addValidationError("Template cycle detected at "+n.getId());
362        }
363    }
364}