diff --git a/README.md b/README.md index def253da5..f4d53ad3e 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,7 @@ the common pattern in this situation is to copy jobs to create new ones, these " diverging from their original "template" and consequently it becomes difficult to maintain consistency between these jobs. -The Job DSL plugin attempts to solve this problem by allowing jobs to be defined in a programmatic form in a human -readable file. Writing such a file is feasible without being a Jenkins expert as the configuration from the web UI +The Job DSL plugin attempts to solve this problem by allowing jobs to be defined in a programmatic form in a human-readable file. Writing such a file is feasible without being a Jenkins expert as the configuration from the web UI translates intuitively into code. ![configuration form](docs/images/pipeline.png) @@ -113,10 +112,7 @@ generated at runtime by introspecting the plugins that have been installed. Jenkins saves the configuration of each job in a XML file. The Job DSL plugin is in principle a generator for these XML files, translating the DSL code into XML. If a configuration option is not available in the high-level DSL, it is possible to generate the XML directly using a -[Configure Block](https://github.com/jenkinsci/job-dsl-plugin/wiki/The-Configure-Block). Use the -[Job DSL Playground](http://job-dsl.herokuapp.com/) to create and test your configure blocks. Please note that the -playground only supports the DSL API that is available in the online -[API Reference](https://jenkinsci.github.io/job-dsl-plugin/). +[Configure Block](https://github.com/jenkinsci/job-dsl-plugin/wiki/The-Configure-Block). Find the complete documentation on the [Wiki](https://github.com/jenkinsci/job-dsl-plugin/wiki). diff --git a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Folder/authorization.groovy b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Folder/authorization.groovy index 9b14bcfd9..62ecdce51 100644 --- a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Folder/authorization.groovy +++ b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Folder/authorization.groovy @@ -1,30 +1,50 @@ -// gives permission for the special authenticated group to create jobs in the folder +// gives permission to create jobs in the folder folder('example-1') { authorization { permission('hudson.model.Item.Create:authenticated') + // requires matrix-auth > 3.0 + permission('GROUP:hudson.model.Item.Create:group1') + permission('USER:hudson.model.Item.Create:user1') + groupPermission('hudson.model.Item.Create', 'group2') + userPermission('hudson.model.Item.Create', 'user2') } } -// gives discover permission for the special anonymous user +// gives discover permission folder('example-2') { authorization { permission('hudson.model.Item.Discover', 'anonymous') + // requires matrix-auth > 3.0 + permission('USER:hudson.model.Item.Discover:anonymous') + userPermission('hudson.model.Item.Discover', 'anonymous') } } -// gives all permissions to the special anonymous user +// gives all permissions folder('example-3') { authorization { permissionAll('anonymous') + // requires matrix-auth > 3.0 + userPermissionAll('user1') + groupPermissionAll('group1') } } -// gives the hudson.model.Item.Discover and hudson.model.Item.Create permission to jill +// gives the hudson.model.Item.Discover and hudson.model.Item.Create permissions folder('example-4') { authorization { permissions('jill', [ 'hudson.model.Item.Create', 'hudson.model.Item.Discover' ]) + // requires matrix-auth > 3.0 + userPermissions('user1', [ + 'hudson.model.Item.Create', + 'hudson.model.Item.Discover' + ]) + groupPermissions('group1', [ + 'hudson.model.Item.Create', + 'hudson.model.Item.Discover' + ]) } } diff --git a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Job/authorization.groovy b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Job/authorization.groovy index 52311448c..1c06af3dd 100644 --- a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Job/authorization.groovy +++ b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/Job/authorization.groovy @@ -1,32 +1,50 @@ -// add a permission for the special authenticated group to see the workspace of the job +// add group permissions to see the workspace of the job job('example-1') { authorization { permission('hudson.model.Item.Workspace:authenticated') + // requires matrix-auth > 3.0 + permission('GROUP:hudson.model.Item.Workspace:group1') + groupPermission('hudson.model.Item.Workspace', 'group2') } } -// adds the build permission for the special anonymous user +// adds the build permission to users job('example-2') { authorization { permission('hudson.model.Item.Build', 'anonymous') + // requires matrix-auth > 3.0 + permission('USER:hudson.model.Item.Build:user1') + userPermission('hudson.model.Item.Build', 'user2') } } -// add all permissions for user joe, blocking inheritance of the global +// add all permissions to users or groups, blocking inheritance of the global // authorization matrix job('example-3') { authorization { permissionAll('joe') blocksInheritance() + // requires matrix-auth > 3.0 + userPermissionAll('user1') + groupPermissionAll('group1') } } -// gives the hudson.model.Item.Discover and hudson.model.Item.Create permission to jill +// gives the hudson.model.Item.Discover and hudson.model.Item.Create permission to user resp. groups job('example-4') { authorization { permissions('jill', [ 'hudson.model.Item.Create', 'hudson.model.Item.Discover' ]) + // requires matrix-auth > 3.0 + userPermissions('user1', [ + 'hudson.model.Item.Create', + 'hudson.model.Item.Discover' + ]) + groupPermissions('group1', [ + 'hudson.model.Item.Create', + 'hudson.model.Item.Discover' + ]) } } diff --git a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/helpers/JobAuthorizationContext/permission.groovy b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/helpers/JobAuthorizationContext/permission.groovy index 0d4c49125..b7ccbe0fc 100644 --- a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/helpers/JobAuthorizationContext/permission.groovy +++ b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/helpers/JobAuthorizationContext/permission.groovy @@ -1,13 +1,19 @@ -// add a permission for the special authenticated group to see the workspace of the job +// add permission to see the workspace of the job job('example-1') { authorization { permission('hudson.model.Item.Workspace:authenticated') + // requires matrix-auth > 3.0 + permission('GROUP:hudson.model.Item.Workspace:group1') + groupPermission('hudson.model.Item.Workspace', 'group2') } } -// adds the build permission for the special anonymous user +// adds the build permission to users job('example-2') { authorization { permission('hudson.model.Item.Build', 'anonymous') + // requires matrix-auth > 3.0 + permission('USER:hudson.model.Item.Workspace:user1') + userPermission('hudson.model.Item.Workspace', 'user2') } } diff --git a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/helpers/JobAuthorizationContext/permissionAll.groovy b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/helpers/JobAuthorizationContext/permissionAll.groovy index 85363aa00..1f87d67f1 100644 --- a/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/helpers/JobAuthorizationContext/permissionAll.groovy +++ b/job-dsl-core/src/main/docs/examples/javaposse/jobdsl/dsl/helpers/JobAuthorizationContext/permissionAll.groovy @@ -1,6 +1,9 @@ -// add all permissions for user joe -job('example') { +// add all permissions +job('example-1') { authorization { permissionAll('joe') + // requires matrix-auth > 3.0 + userPermissionAll('user1') + groupPermissionAll('group1') } } diff --git a/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/AuthorizationContext.groovy b/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/AuthorizationContext.groovy index 877cc3e81..8ad9ccba6 100644 --- a/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/AuthorizationContext.groovy +++ b/job-dsl-core/src/main/groovy/javaposse/jobdsl/dsl/helpers/AuthorizationContext.groovy @@ -2,6 +2,7 @@ package javaposse.jobdsl.dsl.helpers import javaposse.jobdsl.dsl.AbstractContext import javaposse.jobdsl.dsl.JobManagement +import javaposse.jobdsl.dsl.RequiresPlugin import static javaposse.jobdsl.dsl.Preconditions.checkArgument import static javaposse.jobdsl.dsl.Preconditions.checkNotNullOrEmpty @@ -11,6 +12,10 @@ import static javaposse.jobdsl.dsl.Preconditions.checkNotNullOrEmpty */ class AuthorizationContext extends AbstractContext { private final String authorizationMatrixPropertyClassName + private final enum AuthType { + GROUP, + USER; + } Set permissions = new LinkedHashSet() AuthorizationContext(JobManagement jobManagement, String authorizationMatrixPropertyClassName) { @@ -27,18 +32,56 @@ class AuthorizationContext extends AbstractContext { } } + /** + * Adds all available permissions for the user. + * + * @since 1.88 + */ + @RequiresPlugin(id = 'matrix-auth', minimumVersion = '3.0') + void userPermissionAll(String userName) { + availablePermissions.each { + addAuthTypedPermission(AuthType.USER, it, userName) + } + } + + /** + * Adds all available permissions for the group. + * + * @since 1.88 + */ + @RequiresPlugin(id = 'matrix-auth', minimumVersion = '3.0') + void groupPermissionAll(String groupName) { + availablePermissions.each { + addAuthTypedPermission(AuthType.GROUP, it, groupName) + } + } + /** * Adds a specific permission. * - * @param permission a permission string in {@code ':'} format, e.g. - * {@code 'hudson.model.Item.Create:authenticated'} + * @param permission a permission string in {@code '[USER:|GROUP:]:'} format, e.g. + * {@code '[USER:|GROUP:]hudson.model.Item.Create:authenticated'} */ void permission(String permission) { checkNotNullOrEmpty(permission, 'permission must not be null or empty') - checkArgument(permission.contains(':'), 'permission must be :') + checkArgument(permission.contains(':'), 'permission must be [USER:|GROUP:]:') - String[] permissionAndUser = permission.split(':', 2) - this.permission(permissionAndUser[0], permissionAndUser[1]) + String[] permissionParts = permission.split(':', 3) + String prefix = permissionParts[0] + if (permissionParts.size() == 3) { + List authTypes = AuthType.values()*.toString() + checkArgument(authTypes.contains(prefix), + "Prefix must be ${authTypes.join(' or ')}") + } + if (permissionParts.size() == 2) { + this.permission(permissionParts[0], permissionParts[1]) + } else if (prefix == AuthType.USER.toString()) { + jobManagement.requireMinimumPluginVersion('matrix-auth', '3.0') + this.userPermission(permissionParts[1], permissionParts[2]) + } else if (prefix == AuthType.GROUP.toString()) { + jobManagement.requireMinimumPluginVersion('matrix-auth', '3.0') + this.groupPermission(permissionParts[1], permissionParts[2]) + } } /** @@ -52,6 +95,34 @@ class AuthorizationContext extends AbstractContext { addPermission(permission, user) } + /** + * Adds a specific permission, but breaks apart the permission from the user name. + * + * @since 1.88 + */ + @RequiresPlugin(id = 'matrix-auth', minimumVersion = '3.0') + void userPermission(String permission, String userName) { + checkArgument( + availablePermissions.contains(permission), + "permission must be one of ${availablePermissions.join(',')}" + ) + addAuthTypedPermission(AuthType.USER, permission, userName) + } + + /** + * Adds a specific permission, but breaks apart the permission from the group name. + * + * @since 1.88 + */ + @RequiresPlugin(id = 'matrix-auth', minimumVersion = '3.0') + void groupPermission(String permission, String groupName) { + checkArgument( + availablePermissions.contains(permission), + "permission must be one of ${availablePermissions.join(',')}" + ) + addAuthTypedPermission(AuthType.GROUP, permission, groupName) + } + /** * Adds a set of permissions for a user. * @@ -63,10 +134,37 @@ class AuthorizationContext extends AbstractContext { } } + /** + * Adds a set of permissions for a user. + * + * @since 1.88 + */ + void userPermissions(String userName, Iterable permissionsList) { + permissionsList.each { + this.addAuthTypedPermission(AuthType.USER, it, userName) + } + } + + /** + * Adds a set of permissions for a group. + * + * @since 1.88 + */ + void groupPermissions(String groupName, Iterable permissionsList) { + permissionsList.each { + this.addAuthTypedPermission(AuthType.GROUP, it, groupName) + } + } + protected void addPermission(String permission, String user) { permissions << "${permission}:${user}".toString() } + @RequiresPlugin(id = 'matrix-auth', minimumVersion = '3.0') + protected void addAuthTypedPermission(AuthType authType, String permission, String user) { + permissions << "${authType.toString()}:${permission}:${user}".toString() + } + protected Set getAvailablePermissions() { jobManagement.getPermissions(authorizationMatrixPropertyClassName) } diff --git a/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/FolderSpec.groovy b/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/FolderSpec.groovy index 907cda85d..08d90f29c 100644 --- a/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/FolderSpec.groovy +++ b/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/FolderSpec.groovy @@ -98,23 +98,50 @@ class FolderSpec extends Specification { when: folder.authorization { permission('hudson.model.Item.Build:jill') + permission('USER:hudson.model.Item.Build:andi') + permission('GROUP:hudson.model.Item.Build:admin') permission('hudson.model.Item.Configure', 'jack') + userPermission('hudson.model.Item.Configure', 'foo') + groupPermission('hudson.model.Item.Configure', 'bar') permissionAll('anonymous') + userPermissionAll('alex') + groupPermissionAll('devs') permissions('janice', [ 'hudson.model.Item.Build', 'hudson.model.Item.Configure' ]) + userPermissions('pedro', [ + 'hudson.model.Item.Build', + 'hudson.model.Item.Configure' + ]) + groupPermissions('tanya', [ + 'hudson.model.Item.Build', + 'hudson.model.Item.Configure' + ]) } then: NodeList permissions = folder.node.properties[0]."$propertyName"[0].permission - permissions.size() == 6 - permissions[0].text() == 'hudson.model.Item.Build:jill' - permissions[1].text() == 'hudson.model.Item.Configure:jack' - permissions[2].text() == 'hudson.model.Item.Build:anonymous' - permissions[3].text() == 'hudson.model.Item.Configure:anonymous' - permissions[4].text() == 'hudson.model.Item.Build:janice' - permissions[5].text() == 'hudson.model.Item.Configure:janice' + permissions.size() == 18 + int index = -1 + permissions[++index].text() == 'hudson.model.Item.Build:jill' + permissions[++index].text() == 'USER:hudson.model.Item.Build:andi' + permissions[++index].text() == 'GROUP:hudson.model.Item.Build:admin' + permissions[++index].text() == 'hudson.model.Item.Configure:jack' + permissions[++index].text() == 'USER:hudson.model.Item.Configure:foo' + permissions[++index].text() == 'GROUP:hudson.model.Item.Configure:bar' + permissions[++index].text() == 'hudson.model.Item.Build:anonymous' + permissions[++index].text() == 'hudson.model.Item.Configure:anonymous' + permissions[++index].text() == 'USER:hudson.model.Item.Build:alex' + permissions[++index].text() == 'USER:hudson.model.Item.Configure:alex' + permissions[++index].text() == 'GROUP:hudson.model.Item.Build:devs' + permissions[++index].text() == 'GROUP:hudson.model.Item.Configure:devs' + permissions[++index].text() == 'hudson.model.Item.Build:janice' + permissions[++index].text() == 'hudson.model.Item.Configure:janice' + permissions[++index].text() == 'USER:hudson.model.Item.Build:pedro' + permissions[++index].text() == 'USER:hudson.model.Item.Configure:pedro' + permissions[++index].text() == 'GROUP:hudson.model.Item.Build:tanya' + permissions[++index].text() == 'GROUP:hudson.model.Item.Configure:tanya' } def 'call properties'() { diff --git a/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/JobSpec.groovy b/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/JobSpec.groovy index 06d28f97b..02a2095f9 100644 --- a/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/JobSpec.groovy +++ b/job-dsl-core/src/test/groovy/javaposse/jobdsl/dsl/JobSpec.groovy @@ -141,6 +141,57 @@ class JobSpec extends Specification { 1 * jobManagement.requireMinimumPluginVersion('matrix-auth', '1.2') } + def 'call authorization with matrix-auth >= 3.2'() { + setup: + jobManagement.getPermissions('hudson.security.AuthorizationMatrixProperty') >> [ + 'hudson.model.Item.Configure', + 'hudson.model.Item.Read', + 'hudson.model.Run.Update', + ] + + when: + job.authorization { + userPermissionAll('tanya') + groupPermissionAll('devs') + permission('USER:hudson.model.Run.Update:alex') + userPermission('hudson.model.Run.Update', 'sam') + groupPermission('hudson.model.Item.Configure', 'admins') + userPermissions('pedro', [ + 'hudson.model.Item.Configure', + 'hudson.model.Item.Read', + ]) + groupPermissions('robin', [ + 'hudson.model.Item.Configure', + 'hudson.model.Item.Read', + ]) + } + + then: + with(job.node.properties[0].'hudson.security.AuthorizationMatrixProperty'[0]) { + children().size() == 14 + blocksInheritance[0].value() == false + permission.size() == 13 + int index = -1 + permission[++index].text() == 'USER:hudson.model.Item.Configure:tanya' + permission[++index].text() == 'USER:hudson.model.Item.Read:tanya' + permission[++index].text() == 'USER:hudson.model.Run.Update:tanya' + + permission[++index].text() == 'GROUP:hudson.model.Item.Configure:devs' + permission[++index].text() == 'GROUP:hudson.model.Item.Read:devs' + permission[++index].text() == 'GROUP:hudson.model.Run.Update:devs' + + permission[++index].text() == 'USER:hudson.model.Run.Update:alex' + permission[++index].text() == 'USER:hudson.model.Run.Update:sam' + permission[++index].text() == 'GROUP:hudson.model.Item.Configure:admins' + + permission[++index].text() == 'USER:hudson.model.Item.Configure:pedro' + permission[++index].text() == 'USER:hudson.model.Item.Read:pedro' + permission[++index].text() == 'GROUP:hudson.model.Item.Configure:robin' + permission[++index].text() == 'GROUP:hudson.model.Item.Read:robin' + } + 19 * jobManagement.requireMinimumPluginVersion('matrix-auth', '3.0') + } + def 'call authorization with blocksInheritance'() { setup: jobManagement.getPermissions('hudson.security.AuthorizationMatrixProperty') >> [