diff --git a/RELEASE.md b/RELEASE.md index 293e9e3718b..22b321d7035 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,7 +1,14 @@ -Release 1.4 +Release 1.4.0.2 =========== -Date: 10/27/2011 +Date: 11/9/2011 + +* 1.4.0.2 + * [#467 - user not in aclpolicy can perform unauthorized actions](http://rundeck.lighthouseapp.com/projects/59277/tickets/467-user-not-in-aclpolicy-can-perform-unauthorized-actions) + * [#469 - Create job via API can fail due to authorization bug](http://rundeck.lighthouseapp.com/projects/59277/tickets/469-create-job-via-api-can-fail-due-to-authorization-bug) +* 1.4.0.1 + * [#464 - NPE on Execute page](http://rundeck.lighthouseapp.com/projects/59277/tickets/464-npe-on-execute-page) + Tickets: diff --git a/core/build.gradle b/core/build.gradle index 397a3fc2d32..2efdc4a891e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -2,7 +2,7 @@ import org.apache.tools.ant.filters.ReplaceTokens apply plugin: 'java' sourceCompatibility = 1.5 -version = '1.4.0.1' +version = '1.4.0.2' archivesBaseName = 'rundeck-core' defaultTasks 'clean','assemble' repositories { diff --git a/core/src/main/java/com/dtolabs/rundeck/core/authorization/BaseAuthorization.java b/core/src/main/java/com/dtolabs/rundeck/core/authorization/BaseAuthorization.java new file mode 100644 index 00000000000..bddb15e4eab --- /dev/null +++ b/core/src/main/java/com/dtolabs/rundeck/core/authorization/BaseAuthorization.java @@ -0,0 +1,227 @@ +/* + * Copyright 2011 DTO Solutions, Inc. (http://dtosolutions.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* +* BasAuthorization.java +* +* User: Greg Schueler greg@dtosolutions.com +* Created: 11/7/11 9:34 AM +* +*/ +package com.dtolabs.rundeck.core.authorization; + +import com.dtolabs.rundeck.core.common.Framework; +import org.apache.log4j.Logger; + +import javax.security.auth.Subject; +import java.io.File; +import java.io.PrintStream; +import java.security.Principal; +import java.util.*; + +/** + * BasAuthorization is ... + * + * @author Greg Schueler greg@dtosolutions.com + */ +public abstract class BaseAuthorization implements Authorization, LegacyAuthorization { + final Framework framework; + final File baseDir; + final Explanation explanation = new Explanation() { + + public String toString() { + return "\t" + getDescription() + " => " + getCode(); + } + + public void describe(final PrintStream out) { + out.println(toString()); + } + + public Code getCode() { + return getResultCode(); + } + }; + + protected abstract Logger getLogger(); + protected abstract String getDescription(); + + protected abstract Explanation.Code getResultCode(); + + protected abstract boolean isAuthorized(); + + final class BaseAuthorizationDecision implements Decision { + + Map resource; + String action; + Set environment; + Subject subject; + + BaseAuthorizationDecision(final Map resource, final String action, + final Set environment, + final Subject subject) { + this.resource = resource; + this.action = action; + this.environment = environment; + this.subject = subject; + } + + public boolean isAuthorized() { + return BaseAuthorization.this.isAuthorized(); + } + + + public Map getResource() { + return resource; + } + + + public String getAction() { + return action; + } + + + public Set getEnvironment() { + return environment; + } + + + public Subject getSubject() { + return subject; + } + + public Explanation explain() { + return explanation; + } + + public long evaluationDuration() { + return 0; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Decision for: "); + builder.append("res<"); + final Iterator> riter = resource.entrySet().iterator(); + while (riter.hasNext()) { + final Map.Entry s = riter.next(); + builder.append(s.getKey()).append(':').append(s.getValue()); + if (riter.hasNext()) { + builder.append(", "); + } + } + + builder.append("> subject<"); + final Iterator iter = subject.getPrincipals().iterator(); + while (iter.hasNext()) { + final Principal principal = iter.next(); + builder.append(principal.getClass().getSimpleName()); + builder.append(':'); + builder.append(principal.getName()); + if (iter.hasNext()) { + builder.append(' '); + } + } + + builder.append("> action<"); + builder.append(action); + + builder.append("> env<"); + final Iterator eiter = environment.iterator(); + while (eiter.hasNext()) { + final Attribute a = eiter.next(); + builder.append(a); + if (eiter.hasNext()) { + builder.append(", "); + } + } + builder.append(">"); + builder.append(": authorized: "); + builder.append(isAuthorized()); + builder.append(": "); + builder.append(explanation.toString()); + + return builder.toString(); + } + + } + + /** + * Default constructor + * + * @param framework + * @param aclBaseDir + */ + public BaseAuthorization(final Framework framework, final File aclBaseDir) { + this.framework = framework; + this.baseDir = aclBaseDir; + } + + + public String[] getMatchedRoles() { + return new String[0]; + } + + + public String listMatchedRoles() { + return ""; + } + + + public boolean authorizeScript(final String user, final String project, final String adhocScript) throws + AuthorizationException { + return isAuthorized(); + } + + + public Decision evaluate(final Map resource, final Subject subject, + final String action, final Set environment) { + final BaseAuthorizationDecision decision = new BaseAuthorizationDecision(resource, action, + environment, subject); + final StringBuilder sb = new StringBuilder(); + sb.append("Evaluating ").append(decision).append(" (").append(decision.evaluationDuration()).append("ms)") + .append(':'); + + sb.append(decision.explain().toString()); + + getLogger().info(sb.toString()); + return decision; + } + + + public Set evaluate(final Set> resources, final Subject subject, + final Set actions, final Set environment) { + + final Set decisions = new HashSet(); + for (final Map resource : resources) { + for (final String action : actions) { + decisions.add(new BaseAuthorizationDecision(resource, action, environment, subject)); + } + } + for (final Decision decision : decisions) { + + final StringBuilder sb = new StringBuilder(); + sb.append("Evaluating ").append(decision).append(" (").append(decision.evaluationDuration()).append("ms)") + .append(':'); + + sb.append(decision.explain().toString()); + + getLogger().info(sb.toString()); + } + + return decisions; + } +} diff --git a/core/src/main/java/com/dtolabs/rundeck/core/authorization/DenyAuthorization.java b/core/src/main/java/com/dtolabs/rundeck/core/authorization/DenyAuthorization.java new file mode 100644 index 00000000000..efbe7053285 --- /dev/null +++ b/core/src/main/java/com/dtolabs/rundeck/core/authorization/DenyAuthorization.java @@ -0,0 +1,68 @@ +/* + * Copyright 2011 DTO Solutions, Inc. (http://dtosolutions.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.dtolabs.rundeck.core.authorization; + + +import com.dtolabs.rundeck.core.common.Framework; +import org.apache.log4j.Logger; + +import java.io.File; + +/** + * Provides trivial DENY implementation of {@link com.dtolabs.rundeck.core.authorization.Authorization} interface. + */ +public class DenyAuthorization extends BaseAuthorization { + private final static Logger logger = Logger.getLogger(DenyAuthorization.class); + + public DenyAuthorization(final Framework framework, final File aclBaseDir) { + super(framework, aclBaseDir); + } + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + protected String getDescription() { + return this.getClass().getName() + ": Deny all authorization."; + } + + @Override + protected Explanation.Code getResultCode() { + return Explanation.Code.REJECTED_DENIED; + } + + @Override + protected boolean isAuthorized() { + return false; + } + + /** + * Factory method returning an instance implementing the {@link com.dtolabs.rundeck.core.authorization.Authorization} + * interface. + * + * @param framework Framework instance + * @param aclBasedir Directory where the ACLs reside. + * + * @return + */ + public static Authorization create(final Framework framework, final File aclBasedir) { + return new DenyAuthorization(framework, aclBasedir); + } + +} diff --git a/core/src/main/java/com/dtolabs/rundeck/core/authorization/NoAuthorization.java b/core/src/main/java/com/dtolabs/rundeck/core/authorization/NoAuthorization.java index e849196a3a7..718dc9b7c6f 100644 --- a/core/src/main/java/com/dtolabs/rundeck/core/authorization/NoAuthorization.java +++ b/core/src/main/java/com/dtolabs/rundeck/core/authorization/NoAuthorization.java @@ -18,164 +18,54 @@ import com.dtolabs.rundeck.core.common.Framework; +import org.apache.log4j.Logger; -import javax.security.auth.Subject; import java.io.File; -import java.io.PrintStream; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; /** - * Provides trivial implementation of {@link Authorization} interface. - * + * Provides trivial ALLOW implementation of {@link Authorization} interface. + * * @author alexh */ -public class NoAuthorization implements Authorization, LegacyAuthorization { - final Framework framework; - final File baseDir; +public class NoAuthorization extends BaseAuthorization{ + private final static Logger logger = Logger.getLogger(NoAuthorization.class); - /** - * Factory method returning an instance implementing the {@link Authorization} interface. - * - * @param framework Framework instance - * @param aclBasedir Directory where the ACLs reside. - * @return - */ - public static Authorization create(final Framework framework, final File aclBasedir) { - return new NoAuthorization(framework, aclBasedir); - } - /** - * Default constructor - * - * @param framework - * @param aclBaseDir - */ public NoAuthorization(final Framework framework, final File aclBaseDir) { - this.framework = framework; - this.baseDir = aclBaseDir; - } - - - public String[] getMatchedRoles() { - return new String[0]; + super(framework, aclBaseDir); } - - public String listMatchedRoles() { - return ""; + @Override + protected Logger getLogger() { + return logger; } - - public boolean authorizeScript(String user, String project, String adhocScript) throws AuthorizationException { - return true; + @Override + protected String getDescription() { + return this.getClass().getName() + "NoAuthorization: All authorization allowed."; } - - public Decision evaluate(final Map resource, final Subject subject, - final String action, final Set environment) { - return new Decision() { - - - public boolean isAuthorized() { - return true; - } - - - public Map getResource() { - return resource; - } - - - public String getAction() { - return action; - } - - - public Set getEnvironment() { - return environment; - } - - - public Subject getSubject() { - return subject; - } - - - public Explanation explain() { - return new Explanation() { - - public Code getCode() { - return Code.GRANTED_NO_AUTHORIZATION_ATTEMPTED; - } - - public void describe(PrintStream out) { - out.println("No authorization attempted."); - } - }; - } - - - @Override - public long evaluationDuration() { - return 0; - }}; + @Override + protected Explanation.Code getResultCode() { + return Explanation.Code.GRANTED_NO_AUTHORIZATION_ATTEMPTED; } - - public Set evaluate(Set> resources, final Subject subject, - Set actions, final Set environment) { - - Set decisions = new HashSet(); - for(final Map resource : resources) { - for(final String action : actions) { - decisions.add(new Decision() { - - - public boolean isAuthorized() { - return true; - } - - - public Map getResource() { - return resource; - } - - - public String getAction() { - return action; - } - - - public Set getEnvironment() { - return environment; - } - - - public Subject getSubject() { - return subject; - } - public Explanation explain() { - return new Explanation() { - - public void describe(PrintStream out) { - out.println("No authorization attempted."); - } - - public Code getCode() { - return Code.GRANTED_NO_AUTHORIZATION_ATTEMPTED; - } - }; - } - + @Override + protected boolean isAuthorized() { + return true; + } - @Override - public long evaluationDuration() { - return 0; - }}); - } - } - return decisions; + /** + * Factory method returning an instance implementing the {@link com.dtolabs.rundeck.core.authorization.Authorization} + * interface. + * + * @param framework Framework instance + * @param aclBasedir Directory where the ACLs reside. + * + * @return + */ + public static Authorization create(final Framework framework, final File aclBasedir) { + return new NoAuthorization(framework, aclBasedir); } + } diff --git a/core/src/main/resources/com/dtolabs/launcher/setup/templates/apitoken.aclpolicy.template b/core/src/main/resources/com/dtolabs/launcher/setup/templates/apitoken.aclpolicy.template index 79c3065762a..9a36b9c4b65 100644 --- a/core/src/main/resources/com/dtolabs/launcher/setup/templates/apitoken.aclpolicy.template +++ b/core/src/main/resources/com/dtolabs/launcher/setup/templates/apitoken.aclpolicy.template @@ -15,7 +15,7 @@ for: adhoc: - allow: [run,kill] # allow running/killing adhoc jobs job: - - allow: [read,update,delete,run,kill] # allow read/write/delete/run/kill of all jobs + - allow: [create,read,update,delete,run,kill] # allow create/read/write/delete/run/kill of all jobs node: - allow: [read,run] # allow read/run for all nodes by: diff --git a/plugins/build.gradle b/plugins/build.gradle index badca42af8b..16e7cd8bdfd 100644 --- a/plugins/build.gradle +++ b/plugins/build.gradle @@ -1,6 +1,6 @@ allprojects{ - version = '1.4.0.1' + version = '1.4.0.2' defaultTasks 'clean','build' rundeckPluginVersion= '1.0' } diff --git a/rundeckapp/application.properties b/rundeckapp/application.properties index 7ee57dd4b4e..dc617eddbff 100644 --- a/rundeckapp/application.properties +++ b/rundeckapp/application.properties @@ -2,8 +2,8 @@ #Wed Nov 02 09:45:44 PDT 2011 app.grails.version=1.3.7 app.name=rundeck -app.version=1.4.0.1 -build.ident=1.4.0.1-1 +app.version=1.4.0.2 +build.ident=1.4.0.2-1 plugins.hibernate=1.3.7 plugins.jetty=1.2-SNAPSHOT plugins.mail=0.9 diff --git a/rundeckapp/grails-app/conf/AuthorizationFilters.groovy b/rundeckapp/grails-app/conf/AuthorizationFilters.groovy index 0f83b8871a3..d4590cced60 100644 --- a/rundeckapp/grails-app/conf/AuthorizationFilters.groovy +++ b/rundeckapp/grails-app/conf/AuthorizationFilters.groovy @@ -104,7 +104,7 @@ public class AuthorizationFilters { /** * Check the user has authorization for the actions. */ - authorizationCheck(controller: '*', action: '*') { + postLoginAuthorizationCheck(controller: '*', action: '*') { before = { if (request.invalidApiAuthentication ) { diff --git a/rundeckapp/grails-app/conf/BootStrap.groovy b/rundeckapp/grails-app/conf/BootStrap.groovy index 95f659baaf5..a4bb5d2c699 100644 --- a/rundeckapp/grails-app/conf/BootStrap.groovy +++ b/rundeckapp/grails-app/conf/BootStrap.groovy @@ -10,15 +10,25 @@ import org.apache.log4j.Level import org.apache.log4j.net.SocketAppender import grails.util.GrailsUtil import com.dtolabs.launcher.Setup - +import org.codehaus.groovy.grails.plugins.web.filters.FilterConfig +import org.codehaus.groovy.grails.plugins.web.filters.FilterToHandlerAdapter class BootStrap { def grailsApplication def scheduledExecutionService def executionService + def filterInterceptor def init = { servletContext -> + + filterInterceptor.handlers.sort { FilterToHandlerAdapter handler1, + FilterToHandlerAdapter handler2 -> + FilterConfig filter1 = handler1.filterConfig + FilterConfig filter2 = handler2.filterConfig + filter1.name <=> filter2.name + } + def String rdeckBase if(!grailsApplication.config.rdeck.base){ //look for system property diff --git a/rundeckapp/grails-app/conf/ProjectSelectFilters.groovy b/rundeckapp/grails-app/conf/ProjectSelectFilters.groovy index 44db8bf3d4e..d89e094ad26 100644 --- a/rundeckapp/grails-app/conf/ProjectSelectFilters.groovy +++ b/rundeckapp/grails-app/conf/ProjectSelectFilters.groovy @@ -58,7 +58,7 @@ public class ProjectSelectFilters { * on first user login, set the session.project if it is not set, to the last user project selected, or * to the first project in the available list */ - projectSelection(controller: 'framework', action: '(createProject|selectProject|projectSelect|(create|save|check|edit|view)ResourceModelConfig)',invert:true) { + projectSelection(controller: 'framework', action: '(createProject|selectProject|projectSelect|noProjectAccess|(create|save|check|edit|view)ResourceModelConfig)',invert:true) { before = { if (request.api_version || request.is_api_req) { //only default the project if not an api request @@ -108,7 +108,11 @@ public class ProjectSelectFilters { } session.project = selected if (!selected) { - redirect(action: 'createProject', controller: 'framework') + if (!frameworkService.authorizeApplicationResourceTypeAll(fw, 'project', ['create'])) { + redirect(action: 'noProjectAccess', controller: 'framework') + }else{ + redirect(action: 'createProject', controller: 'framework') + } return false } } diff --git a/rundeckapp/grails-app/controllers/FrameworkController.groovy b/rundeckapp/grails-app/controllers/FrameworkController.groovy index f6af02ecbc4..e2315382c68 100644 --- a/rundeckapp/grails-app/controllers/FrameworkController.groovy +++ b/rundeckapp/grails-app/controllers/FrameworkController.groovy @@ -52,6 +52,14 @@ class FrameworkController { response.setHeader(Constants.X_RUNDECK_ACTION_UNAUTHORIZED_HEADER, request.error) render(template: fragment ? '/common/errorFragment' : '/common/error', model: [:]) } + + def noProjectAccess = { + response.setStatus(403) + request.title = "Unauthorized" + request.error = "No authorized access to projects. Contact your administrator." + response.setHeader(Constants.X_RUNDECK_ACTION_UNAUTHORIZED_HEADER, request.error) + return render(template: '/common/error', model: [:]) + } /** * This action returns a json object informing about whether the user is authorized * to run scripts in the current project context. diff --git a/rundeckapp/grails-app/controllers/MenuController.groovy b/rundeckapp/grails-app/controllers/MenuController.groovy index 5f799bcddd7..d6785eaa5b5 100644 --- a/rundeckapp/grails-app/controllers/MenuController.groovy +++ b/rundeckapp/grails-app/controllers/MenuController.groovy @@ -248,14 +248,12 @@ class MenuController { } // Filter the groups by what the user is authorized to see. - def authorization = frameworkService.getFrameworkFromUserSession(request.session, request).getAuthorizationMgr() - def env = Collections.singleton(new Attribute(URI.create(EnvironmentalContext.URI_BASE +"project"), session.project)) - def decisions = authorization.evaluate(res, request.subject, new HashSet([AuthConstants.ACTION_READ,AuthConstants.ACTION_DELETE,AuthConstants.ACTION_RUN,AuthConstants.ACTION_UPDATE,AuthConstants.ACTION_KILL]), env) + def decisions = frameworkService.authorizeProjectResources(framework,res, new HashSet([AuthConstants.ACTION_READ, AuthConstants.ACTION_DELETE, AuthConstants.ACTION_RUN, AuthConstants.ACTION_UPDATE, AuthConstants.ACTION_KILL]),query.projFilter) log.debug("listWorkflows(evaluate): "+(System.currentTimeMillis()-preeval)); long viewable=System.currentTimeMillis() - def authCreate = frameworkService.authorizeProjectResource(framework, [type: 'resource', kind: 'job'], AuthConstants.ACTION_CREATE, session.project) + def authCreate = frameworkService.authorizeProjectResource(framework, [type: 'resource', kind: 'job'], AuthConstants.ACTION_CREATE, query.projFilter) def Map jobauthorizations=[:] diff --git a/rundeckapp/grails-app/controllers/ScheduledExecutionController.groovy b/rundeckapp/grails-app/controllers/ScheduledExecutionController.groovy index 571d01f8e78..76a633c31c6 100644 --- a/rundeckapp/grails-app/controllers/ScheduledExecutionController.groovy +++ b/rundeckapp/grails-app/controllers/ScheduledExecutionController.groovy @@ -166,7 +166,7 @@ class ScheduledExecutionController { response.setStatus (404) return error.call() } - if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_READ], session.project)) { + if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_READ], scheduledExecution.project)) { return unauthorized("Read Job ${params.id}") } crontab = scheduledExecution.timeAndDateAsBooleanMap() @@ -654,7 +654,7 @@ class ScheduledExecutionController { return redirect(action:index, params:params) } - if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_UPDATE, AuthConstants.ACTION_READ], session.project)) { + if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_UPDATE, AuthConstants.ACTION_READ], scheduledExecution.project)) { return unauthorized("Update Job ${params.id}") } //clear session workflow @@ -771,7 +771,7 @@ class ScheduledExecutionController { boolean failed=false def ScheduledExecution scheduledExecution = scheduledExecutionService.getByIDorUUID( params.id ) - if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_UPDATE], session.project)) { + if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_UPDATE], scheduledExecution.project)) { return [success:false,scheduledExecution:scheduledExecution,message:"Update Job ${scheduledExecution.extid}",unauthorized:true] } @@ -1336,7 +1336,7 @@ class ScheduledExecutionController { redirect(action:index) return; } - if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_READ], session.project)) { + if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_READ], scheduledExecution.project)) { return unauthorized("Read Job ${params.id}") } def newScheduledExecution = new ScheduledExecution() @@ -2204,7 +2204,7 @@ class ScheduledExecutionController { failed=result.failed //try to save workflow - if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_CREATE], session.project)) { + if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_CREATE], scheduledExecution.project)) { scheduledExecution.discard() return [success: false, error: "Unauthorized: Create Job ${scheduledExecution.generateFullName()}", unauthorized: true, scheduledExecution:scheduledExecution] } @@ -2986,7 +2986,7 @@ class ScheduledExecutionController { return chain(controller: 'api', action: 'renderError') } Framework framework = frameworkService.getFrameworkFromUserSession(session, request) - if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_READ], session.project)) { + if (!frameworkService.authorizeProjectJobAll(framework, scheduledExecution, [AuthConstants.ACTION_READ], scheduledExecution.project)) { request.errorCode = "api.error.item.unauthorized" request.errorArgs = ['Read','Job ID', params.id] return new ApiController().renderError() diff --git a/rundeckapp/grails-app/services/ExecutionService.groovy b/rundeckapp/grails-app/services/ExecutionService.groovy index fd0c18022f5..11c2bf5cd3b 100644 --- a/rundeckapp/grails-app/services/ExecutionService.groovy +++ b/rundeckapp/grails-app/services/ExecutionService.groovy @@ -410,7 +410,7 @@ class ExecutionService implements ApplicationContextAware, CommandInterpreter{ } - public logExecution(uri,project,user,issuccess,framework,execId,Date startDate=null, jobExecId=null, jobName=null, jobSummary=null,iscancelled=false, nodesummary=null, abortedby=null){ + public logExecution(uri,project,user,issuccess,execId,Date startDate=null, jobExecId=null, jobName=null, jobSummary=null,iscancelled=false, nodesummary=null, abortedby=null){ def reportMap=[:] def internalLog = org.apache.log4j.Logger.getLogger("ExecutionService") @@ -444,7 +444,7 @@ class ExecutionService implements ApplicationContextAware, CommandInterpreter{ reportMap.author=user reportMap.title= jobSummary?jobSummary:"RunDeck Job Execution" reportMap.status= issuccess ? "succeed":iscancelled?"cancel":"fail" - reportMap.node= null!=nodesummary?nodesummary: framework.getFrameworkNodeName() + reportMap.node= null!=nodesummary?nodesummary: frameworkService.getFrameworkNodeName() reportMap.message=(issuccess?'Job completed successfully':iscancelled?('Job killed by: '+(abortedby?:user)):'Job failed') reportMap.dateCompleted=new Date() @@ -1287,8 +1287,7 @@ class ExecutionService implements ApplicationContextAware, CommandInterpreter{ failedCount=failed.size() totalCount=matched.size() } - def Framework fw = frameworkService.getFramework() - logExecution(null, execution.project, execution.user, "true" == execution.status, fw, exId, + logExecution(null, execution.project, execution.user, "true" == execution.status, exId, execution.dateStarted, jobid, jobname, summarizeJob(scheduledExecution, execution), props.cancelled, node, execution.abortedby) notificationService.triggerJobNotification(props.status == 'true' ? 'success' : 'failure', schedId, [execution: execution,nodestatus:[succeeded:sucCount,failed:failedCount,total:totalCount]]) diff --git a/rundeckapp/grails-app/services/FrameworkService.groovy b/rundeckapp/grails-app/services/FrameworkService.groovy index aad6ab45ee9..04536f94b62 100644 --- a/rundeckapp/grails-app/services/FrameworkService.groovy +++ b/rundeckapp/grails-app/services/FrameworkService.groovy @@ -98,8 +98,22 @@ class FrameworkService implements ApplicationContextAware { return framework.getAuthorizationMgr().authorizeScript(user,project,script) } - def authorizeProjectResource(Framework framework, Map resource, String action, String project){ + def Set authorizeProjectResources(Framework framework, Set resources, Set actions, String project) { + if (null == project) { + throw new IllegalArgumentException("null project") + } + def Set decisions = framework.getAuthorizationMgr().evaluate( + resources, + framework.getAuthenticationMgr().subject, + actions, + Collections.singleton(new Attribute(URI.create(EnvironmentalContext.URI_BASE + "project"), project))) + return decisions + } + def authorizeProjectResource(Framework framework, Map resource, String action, String project){ + if (null == project) { + throw new IllegalArgumentException("null project") + } def Decision decision=framework.getAuthorizationMgr().evaluate( resource, framework.getAuthenticationMgr().subject, @@ -108,7 +122,9 @@ class FrameworkService implements ApplicationContextAware { return decision.isAuthorized() } def authorizeProjectResourceAll(Framework framework, Map resource, Collection actions, String project){ - + if(null==project){ + throw new IllegalArgumentException("null project") + } def decisions=framework.getAuthorizationMgr().evaluate( [resource] as Set, framework.getAuthenticationMgr().subject, @@ -117,7 +133,9 @@ class FrameworkService implements ApplicationContextAware { return !(decisions.find {!it.authorized}) } def authorizeProjectJobAll(Framework framework, ScheduledExecution job, Collection actions, String project){ - + if (null == project) { + throw new IllegalArgumentException("null project") + } def decisions=framework.getAuthorizationMgr().evaluate( [[type:'job',job:job.jobName,group:job.groupPath?:'']] as Set, framework.getAuthenticationMgr().subject, @@ -164,9 +182,15 @@ class FrameworkService implements ApplicationContextAware { framework.getAuthenticationMgr().subject, actions as Set, Collections.singleton(new Attribute(URI.create(EnvironmentalContext.URI_BASE + "application"), 'rundeck'))) - return !(decisions.find {!it.authorized}) } + + def getFrameworkNodeName() { + def rdbase= getRundeckBase() + def Framework fw = Framework.getInstance(rdbase) + fw.setAuthorizationMgr(new DenyAuthorization(fw, new File(Constants.getFrameworkConfigDir(rdbase)))) + return fw.getFrameworkNodeName() + } def getFrameworkFromUserSession( session, request){ if (!initialized) { initialize() @@ -178,9 +202,6 @@ class FrameworkService implements ApplicationContextAware { } return session.Framework; } - def getFramework(){ - return getFrameworkForUserAndRoles(null,null) - } def getFrameworkForUserAndRoles(String user, List rolelist){ return getFrameworkForUserAndRoles(user, rolelist, getRundeckBase()) } @@ -197,6 +218,9 @@ class FrameworkService implements ApplicationContextAware { def author = new SingleUserAclsAuthorization(fw,new File(Constants.getFrameworkConfigDir(rundeckbase)), user, rolelist.toArray(new String[0])) fw.setAuthenticationMgr(authen) fw.setAuthorizationMgr(author) + }else{ + System.err.println("getFrameworkForUserAndRoles: No user/subject authorization") + throw new RuntimeException("Cannot get framework without user, roles: ${user}, ${rolelist}") } fw.setAllowUserInput(false) return fw @@ -208,6 +232,9 @@ class FrameworkService implements ApplicationContextAware { def author = new UserSubjectAuthorization(fw,new File(Constants.getFrameworkConfigDir(rundeckbase)), user, subject) fw.setAuthenticationMgr(authen) fw.setAuthorizationMgr(author) + } else { + System.err.println("getFrameworkForUserAndSubject: No user/subject authorization") + throw new RuntimeException("Cannot get framework without user, subject: ${user}, ${subject}") } fw.setAllowUserInput(false) return fw diff --git a/rundeckapp/grails-app/services/ScheduledExecutionService.groovy b/rundeckapp/grails-app/services/ScheduledExecutionService.groovy index 641677105b5..7dd654afbda 100644 --- a/rundeckapp/grails-app/services/ScheduledExecutionService.groovy +++ b/rundeckapp/grails-app/services/ScheduledExecutionService.groovy @@ -237,9 +237,8 @@ class ScheduledExecutionService { } // Filter the groups by what the user is authorized to see. - def env = Collections.singleton(new Attribute(URI.create(EnvironmentalContext.URI_BASE+"project"), project)) - def decisions = framework.getAuthorizationMgr().evaluate(res, framework.getAuthenticationMgr().subject, - new HashSet([AuthConstants.ACTION_READ]), env) + def decisions = frameworkService.authorizeProjectResources(framework,res, + new HashSet([AuthConstants.ACTION_READ]),project) decisions.each{ if(it.authorized){ @@ -378,20 +377,10 @@ class ScheduledExecutionService { } def userAuthorizedForJob(request,ScheduledExecution se, Framework framework){ - def resource = ["job": se.getJobName(), "group": se.getGroupPath() ?: "",type:'job'] - def environment = Collections.singleton(new Attribute(URI.create(EnvironmentalContext.URI_BASE+"project"), - se.project)) - def Decision d = framework.getAuthorizationMgr().evaluate(resource, request.subject, - AuthConstants.ACTION_READ, environment) - return d.isAuthorized() + return frameworkService.authorizeProjectJobAll(framework,se,[AuthConstants.ACTION_READ],se.project) } def userAuthorizedForAdhoc(request,ScheduledExecution se, Framework framework){ - def resource = [type:'adhoc'] - def environment = Collections.singleton(new Attribute(URI.create(EnvironmentalContext.URI_BASE + "project"), - se.project)) - def Decision d = framework.getAuthorizationMgr().evaluate(resource, request.subject, - AuthConstants.ACTION_RUN, environment) - return d.isAuthorized() + return frameworkService.authorizeProjectResource(framework,[type: 'adhoc'], AuthConstants.ACTION_RUN,se.project) } def scheduleJob(ScheduledExecution se, String oldJobName, String oldGroupName) { diff --git a/rundeckapp/grails-app/views/common/_error.gsp b/rundeckapp/grails-app/views/common/_error.gsp index 6b7651c552a..ad7c474b608 100644 --- a/rundeckapp/grails-app/views/common/_error.gsp +++ b/rundeckapp/grails-app/views/common/_error.gsp @@ -2,13 +2,13 @@ - Error + ${flash.title ?: title ?: 'Error'}
- ${flash.title?flash.title:'Error'} + ${flash.title?:title?:'Error'}
diff --git a/test/api/test-jobs-import-yaml.sh b/test/api/test-jobs-import-yaml.sh index 80d0a5aadb7..122d10afad1 100755 --- a/test/api/test-jobs-import-yaml.sh +++ b/test/api/test-jobs-import-yaml.sh @@ -58,6 +58,7 @@ skipcount=$($XMLSTARLET sel -T -t -v "/result/skipped/@count" $DIR/curl.out) if [ "1" != "$succount" ] ; then errorMsg "Upload was not successful." + echo $($XMLSTARLET sel -T -t -m "/result/failed" -v "job/error" $DIR/curl.out) exit 2 else echo "OK" diff --git a/version.properties b/version.properties index e5142358152..ff55fb4b93a 100644 --- a/version.properties +++ b/version.properties @@ -1,3 +1,3 @@ -version.number=1.4.0.1 +version.number=1.4.0.2 version.release.number=1 version.tag=GA