Browse Source

SONAR-6496 WS permissions/create_template create a permission template

tags/5.2-RC1
Teryk Bellahsene 8 years ago
parent
commit
61050e0245
25 changed files with 2375 additions and 106 deletions
  1. 31
    1
      server/sonar-server/src/main/java/org/sonar/server/permission/PermissionRequestValidator.java
  2. 48
    41
      server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java
  3. 134
    0
      server/sonar-server/src/main/java/org/sonar/server/permission/ws/CreateTemplateAction.java
  4. 3
    0
      server/sonar-server/src/main/java/org/sonar/server/permission/ws/Parameters.java
  5. 67
    0
      server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionTemplateDtoBuilder.java
  6. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWsModule.java
  7. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java
  8. 9
    0
      server/sonar-server/src/main/resources/org/sonar/server/permission/ws/create_template-example.json
  9. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/computation/step/ApplyPermissionsStepTest.java
  10. 12
    13
      server/sonar-server/src/test/java/org/sonar/server/permission/PermissionTemplateServiceTest.java
  11. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupToTemplateActionTest.java
  12. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserToTemplateActionTest.java
  13. 173
    0
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/CreateTemplateActionTest.java
  14. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsModuleTest.java
  15. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupFromTemplateActionTest.java
  16. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserFromTemplateActionTest.java
  17. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPermissionTemplatesTest.java
  18. 8
    0
      server/sonar-server/src/test/resources/org/sonar/server/permission/ws/CreateTemplateActionTest/create_template.json
  19. 40
    16
      sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java
  20. 2
    0
      sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java
  21. 15
    2
      sonar-db/src/main/resources/org/sonar/db/permission/PermissionTemplateMapper.xml
  22. 4
    18
      sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateDaoTest.java
  23. 4
    3
      sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateTesting.java
  24. 1799
    2
      sonar-ws/src/main/gen-java/org/sonarqube/ws/Permissions.java
  25. 15
    0
      sonar-ws/src/main/protobuf/ws-permissions.proto

+ 31
- 1
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionRequestValidator.java View File

@@ -20,15 +20,25 @@

package org.sonar.server.permission;

import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.annotation.Nullable;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.core.permission.ProjectPermissions;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.permission.PermissionTemplateDto;
import org.sonar.server.exceptions.BadRequestException;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static org.sonar.api.security.DefaultGroups.isAnyone;
import static org.sonar.server.ws.WsUtils.checkRequest;

public class PermissionRequestValidator {
public static final String MSG_TEMPLATE_WITH_SAME_NAME = "A template with the name '%s' already exists (case insensitive).";
public static final String MSG_TEMPLATE_NAME_NOT_BLANK = "The template name must not be blank";

private PermissionRequestValidator() {
// static methods only
}
@@ -45,6 +55,26 @@ public class PermissionRequestValidator {

public static void validateNotAnyoneAndAdminPermission(String permission, @Nullable String groupName) {
checkRequest(!GlobalPermissions.SYSTEM_ADMIN.equals(permission) || !isAnyone(groupName),
String.format("It is not possible to add the '%s' permission to the '%s' group.", permission, groupName));
format("It is not possible to add the '%s' permission to the '%s' group.", permission, groupName));
}

public static void validateProjectPattern(@Nullable String projectPattern) {
if (isNullOrEmpty(projectPattern)) {
return;
}

try {
Pattern.compile(projectPattern);
} catch (PatternSyntaxException e) {
throw new BadRequestException(format("The 'projectPattern' parameter must be a valid Java regular expression. '%s' was passed", projectPattern));
}
}

public static void validateTemplateNameForUpdate(DbSession dbSession, DbClient dbClient, String templateName, long templateId) {
checkRequest(!templateName.isEmpty(), MSG_TEMPLATE_NAME_NOT_BLANK);

PermissionTemplateDto permissionTemplateWithSameName = dbClient.permissionTemplateDao().selectByName(dbSession, templateName);
checkRequest(permissionTemplateWithSameName == null || permissionTemplateWithSameName.getId() == templateId,
format(MSG_TEMPLATE_WITH_SAME_NAME, templateName));
}
}

+ 48
- 41
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java View File

@@ -21,26 +21,31 @@
package org.sonar.server.permission;

import com.google.common.collect.Lists;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.Uuids;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.permission.PermissionTemplateDao;
import org.sonar.db.permission.PermissionTemplateDto;
import org.sonar.db.user.GroupDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;

import static java.lang.String.format;
import static org.sonar.server.permission.PermissionPrivilegeChecker.checkGlobalAdminUser;
import static org.sonar.server.permission.PermissionPrivilegeChecker.checkProjectAdminUserByComponentKey;
import static org.sonar.server.permission.PermissionRequestValidator.MSG_TEMPLATE_NAME_NOT_BLANK;
import static org.sonar.server.permission.PermissionRequestValidator.MSG_TEMPLATE_WITH_SAME_NAME;
import static org.sonar.server.permission.PermissionRequestValidator.validateProjectPattern;
import static org.sonar.server.permission.PermissionRequestValidator.validateTemplateNameForUpdate;
import static org.sonar.server.ws.WsUtils.checkRequest;

/**
* Used by ruby code <pre>Internal.permission_templates</pre>
@@ -91,19 +96,39 @@ public class PermissionTemplateService {
return permissionTemplates;
}

public PermissionTemplate createPermissionTemplate(String name, @Nullable String description, @Nullable String keyPattern) {
checkGlobalAdminUser(userSession);
validateTemplateName(null, name);
validateKeyPattern(keyPattern);
PermissionTemplateDto permissionTemplateDto = permissionTemplateDao.insertPermissionTemplate(name, description, keyPattern);
return PermissionTemplate.create(permissionTemplateDto);
public PermissionTemplate createPermissionTemplate(String name, @Nullable String description, @Nullable String projectKeyPattern) {
DbSession dbSession = dbClient.openSession(false);

try {
checkGlobalAdminUser(userSession);
validateTemplateNameForCreation(dbSession, name);
validateProjectPattern(projectKeyPattern);
Date now = new Date(System2.INSTANCE.now());
PermissionTemplateDto permissionTemplateDto = permissionTemplateDao.insert(dbSession, new PermissionTemplateDto()
.setKee(Uuids.create())
.setName(name)
.setKeyPattern(projectKeyPattern)
.setDescription(description)
.setCreatedAt(now)
.setUpdatedAt(now));
dbSession.commit();
return PermissionTemplate.create(permissionTemplateDto);
} finally {
dbClient.closeSession(dbSession);
}
}

public void updatePermissionTemplate(Long templateId, String newName, @Nullable String newDescription, @Nullable String newKeyPattern) {
checkGlobalAdminUser(userSession);
validateTemplateName(templateId, newName);
validateKeyPattern(newKeyPattern);
permissionTemplateDao.updatePermissionTemplate(templateId, newName, newDescription, newKeyPattern);
public void updatePermissionTemplate(Long templateId, String newName, @Nullable String newDescription, @Nullable String projectPattern) {
DbSession dbSession = dbClient.openSession(false);

try {
checkGlobalAdminUser(userSession);
validateTemplateNameForUpdate(dbSession, dbClient, newName, templateId);
validateProjectPattern(projectPattern);
permissionTemplateDao.update(templateId, newName, newDescription, projectPattern);
} finally {
dbClient.closeSession(dbSession);
}
}

public void deletePermissionTemplate(Long templateId) {
@@ -186,32 +211,14 @@ public class PermissionTemplateService {
}
}

private void validateTemplateName(@Nullable Long templateId, String templateName) {
if (StringUtils.isEmpty(templateName)) {
String errorMsg = "Name can't be blank";
throw new BadRequestException(errorMsg);
}
List<PermissionTemplateDto> existingTemplates = permissionTemplateDao.selectAllPermissionTemplates();
if (existingTemplates != null) {
for (PermissionTemplateDto existingTemplate : existingTemplates) {
if ((templateId == null || !existingTemplate.getId().equals(templateId)) && (existingTemplate.getName().equals(templateName))) {
String errorMsg = "A template with that name already exists";
throw new BadRequestException(errorMsg);
}
}
}
}
/**
* @deprecated since 5.2
*/
@Deprecated
private void validateTemplateNameForCreation(DbSession dbSession, String templateName) {
checkRequest(!templateName.isEmpty(), MSG_TEMPLATE_NAME_NOT_BLANK);

private static void validateKeyPattern(@Nullable String keyPattern) {
if (StringUtils.isEmpty(keyPattern)) {
return;
}
try {
Pattern.compile(keyPattern);
} catch (PatternSyntaxException e) {
String errorMsg = "Invalid pattern: " + keyPattern + ". Should be a valid Java regular expression.";
throw new BadRequestException(errorMsg);
}
PermissionTemplateDto permissionTemplateWithSameName = dbClient.permissionTemplateDao().selectByName(dbSession, templateName);
checkRequest(permissionTemplateWithSameName == null, format(MSG_TEMPLATE_WITH_SAME_NAME, templateName));
}

}

+ 134
- 0
server/sonar-server/src/main/java/org/sonar/server/permission/ws/CreateTemplateAction.java View File

@@ -0,0 +1,134 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.permission.ws;

import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.permission.PermissionTemplateDto;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Permissions;
import org.sonarqube.ws.Permissions.PermissionTemplate;

import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.server.permission.PermissionPrivilegeChecker.checkGlobalAdminUser;
import static org.sonar.server.permission.PermissionRequestValidator.MSG_TEMPLATE_NAME_NOT_BLANK;
import static org.sonar.server.permission.PermissionRequestValidator.MSG_TEMPLATE_WITH_SAME_NAME;
import static org.sonar.server.permission.PermissionRequestValidator.validateProjectPattern;
import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_DESCRIPTION;
import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_NAME;
import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_PATTERN;
import static org.sonar.server.permission.ws.PermissionTemplateDtoBuilder.create;
import static org.sonar.server.ws.WsUtils.checkRequest;
import static org.sonar.server.ws.WsUtils.writeProtobuf;

public class CreateTemplateAction implements PermissionsWsAction {
private final DbClient dbClient;
private final UserSession userSession;
private final System2 system;

public CreateTemplateAction(DbClient dbClient, UserSession userSession, System2 system) {
this.dbClient = dbClient;
this.userSession = userSession;
this.system = system;
}

@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction("create_template")
.setDescription("Create a permission template.<br />" +
"It requires administration permissions to access.")
.setResponseExample(getClass().getResource("create_template-example.json"))
.setSince("5.2")
.setPost(true)
.setHandler(this);

action.createParam(PARAM_TEMPLATE_NAME)
.setRequired(true)
.setDescription("Name")
.setExampleValue("Financial Service Permissions");

action.createParam(PARAM_TEMPLATE_PATTERN)
.setDescription("Project key pattern. Must be a valid Java regular expression")
.setExampleValue(".*\\.finance\\..*");

action.createParam(PARAM_TEMPLATE_DESCRIPTION)
.setDescription("Description")
.setExampleValue("Permissions for all projects related to the financial service");
}

@Override
public void handle(Request wsRequest, Response wsResponse) throws Exception {
String name = wsRequest.mandatoryParam(PARAM_TEMPLATE_NAME);
String description = wsRequest.param(PARAM_TEMPLATE_DESCRIPTION);
String projectPattern = wsRequest.param(PARAM_TEMPLATE_PATTERN);

DbSession dbSession = dbClient.openSession(false);
try {
checkGlobalAdminUser(userSession);
validateTemplateNameForCreation(dbSession, name);
validateProjectPattern(projectPattern);

PermissionTemplateDto permissionTemplate = insertTemplate(dbSession, name, description, projectPattern);

Permissions.CreatePermissionTemplateResponse response = buildResponse(permissionTemplate);
writeProtobuf(response, wsRequest, wsResponse);
} finally {
dbClient.closeSession(dbSession);
}
}

private void validateTemplateNameForCreation(DbSession dbSession, String templateName) {
checkRequest(!templateName.isEmpty(), MSG_TEMPLATE_NAME_NOT_BLANK);

PermissionTemplateDto permissionTemplateWithSameName = dbClient.permissionTemplateDao().selectByName(dbSession, templateName);
checkRequest(permissionTemplateWithSameName == null, String.format
(MSG_TEMPLATE_WITH_SAME_NAME, templateName));
}

private PermissionTemplateDto insertTemplate(DbSession dbSession, String name, String description, String projectPattern) {
PermissionTemplateDto template = dbClient.permissionTemplateDao().insert(dbSession, create(system)
.setName(name)
.setDescription(description)
.setProjectKeyPattern(projectPattern)
.toDto());
dbSession.commit();
return template;
}

private Permissions.CreatePermissionTemplateResponse buildResponse(PermissionTemplateDto permissionTemplate) {
PermissionTemplate.Builder permissionTemplateBuilder = PermissionTemplate.newBuilder()
.setKey(permissionTemplate.getKee())
.setName(permissionTemplate.getName())
.setCreatedAt(formatDateTime(permissionTemplate.getCreatedAt()))
.setUpdatedAt(formatDateTime(permissionTemplate.getUpdatedAt()));
if (permissionTemplate.getDescription() != null) {
permissionTemplateBuilder.setDescription(permissionTemplate.getDescription());
}
if (permissionTemplate.getKeyPattern() != null) {
permissionTemplateBuilder.setProjectPattern(permissionTemplate.getKeyPattern());
}
return Permissions.CreatePermissionTemplateResponse.newBuilder().setPermissionTemplate(permissionTemplateBuilder).build();
}
}

+ 3
- 0
server/sonar-server/src/main/java/org/sonar/server/permission/ws/Parameters.java View File

@@ -33,6 +33,9 @@ class Parameters {
static final String PARAM_PROJECT_KEY = "projectKey";
static final String PARAM_USER_LOGIN = "login";
static final String PARAM_TEMPLATE_KEY = "templateKey";
static final String PARAM_TEMPLATE_NAME = "name";
static final String PARAM_TEMPLATE_DESCRIPTION = "description";
static final String PARAM_TEMPLATE_PATTERN = "projectPattern";

private static final String PERMISSION_PARAM_DESCRIPTION = String.format("Permission" +
"<ul>" +

+ 67
- 0
server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionTemplateDtoBuilder.java View File

@@ -0,0 +1,67 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.permission.ws;

import java.util.Date;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.Uuids;
import org.sonar.db.permission.PermissionTemplateDto;

class PermissionTemplateDtoBuilder {
private final System2 system;
private String name;
private String description;
private String projectKeyPattern;

private PermissionTemplateDtoBuilder(System2 system) {
this.system = system;
}

static PermissionTemplateDtoBuilder create(System2 system) {
return new PermissionTemplateDtoBuilder(system);
}

PermissionTemplateDtoBuilder setName(String name) {
this.name = name;
return this;
}

PermissionTemplateDtoBuilder setDescription(String description) {
this.description = description;
return this;
}

PermissionTemplateDtoBuilder setProjectKeyPattern(String projectKeyPattern) {
this.projectKeyPattern = projectKeyPattern;
return this;
}

PermissionTemplateDto toDto() {
long now = system.now();
return new PermissionTemplateDto()
.setName(name)
.setDescription(description)
.setKeyPattern(projectKeyPattern)
.setKee(Uuids.create())
.setCreatedAt(new Date(now))
.setUpdatedAt(new Date(now));
}
}

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/permission/ws/PermissionsWsModule.java View File

@@ -41,6 +41,7 @@ public class PermissionsWsModule extends Module {
AddUserToTemplateAction.class,
RemoveUserFromTemplateAction.class,
AddGroupToTemplateAction.class,
RemoveGroupFromTemplateAction.class);
RemoveGroupFromTemplateAction.class,
CreateTemplateAction.class);
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPermissionTemplates.java View File

@@ -73,7 +73,7 @@ public class RegisterPermissionTemplates {

private void insertDefaultTemplate(String templateName) {
PermissionTemplateDto defaultPermissionTemplate = dbClient.permissionTemplateDao()
.insertPermissionTemplate(templateName, PermissionTemplateDto.DEFAULT.getDescription(), null);
.insert(templateName, PermissionTemplateDto.DEFAULT.getDescription(), null);
addGroupPermission(defaultPermissionTemplate, UserRole.ADMIN, DefaultGroups.ADMINISTRATORS);
addGroupPermission(defaultPermissionTemplate, UserRole.ISSUE_ADMIN, DefaultGroups.ADMINISTRATORS);
addGroupPermission(defaultPermissionTemplate, UserRole.USER, DefaultGroups.ANYONE);

+ 9
- 0
server/sonar-server/src/main/resources/org/sonar/server/permission/ws/create_template-example.json View File

@@ -0,0 +1,9 @@
{
"permissionTemplate": {
"key": "af8cb8cc-1e78-4c4e-8c00-ee8e814009a5",
"name": "Finance",
"description": "Permissions for financial related projects",
"createdAt": "2015-08-25T16:18:48+0200",
"updatedAt": "2015-08-25T16:18:48+0200"
}
}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/computation/step/ApplyPermissionsStepTest.java View File

@@ -105,7 +105,7 @@ public class ApplyPermissionsStepTest extends BaseStepTest {
dbClient.componentDao().insert(dbSession, projectDto);

// Create a permission template containing browse permission for anonymous group
PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insertPermissionTemplate("Default", null, null);
PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insert("Default", null, null);
settings.setProperty("sonar.permission.template.default", permissionTemplateDto.getKee());
dbClient.permissionTemplateDao().insertGroupPermission(permissionTemplateDto.getId(), null, UserRole.USER);
dbSession.commit();

+ 12
- 13
server/sonar-server/src/test/java/org/sonar/server/permission/PermissionTemplateServiceTest.java View File

@@ -44,6 +44,7 @@ import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.tester.UserSessionRule;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -108,7 +109,7 @@ public class PermissionTemplateServiceTest {

@Test
public void should_create_permission_template() {
when(permissionTemplateDao.insertPermissionTemplate(DEFAULT_KEY, DEFAULT_DESC, DEFAULT_PATTERN)).thenReturn(DEFAULT_TEMPLATE);
when(permissionTemplateDao.insert(any(DbSession.class), any(PermissionTemplateDto.class))).thenReturn(DEFAULT_TEMPLATE);

PermissionTemplate permissionTemplate = underTest.createPermissionTemplate(DEFAULT_KEY, DEFAULT_DESC, DEFAULT_PATTERN);

@@ -121,9 +122,9 @@ public class PermissionTemplateServiceTest {
@Test
public void should_enforce_unique_template_name() {
expected.expect(BadRequestException.class);
expected.expectMessage("A template with that name already exists");
expected.expectMessage("A template with the name 'my_template' already exists (case insensitive).");

when(permissionTemplateDao.selectAllPermissionTemplates()).thenReturn(Lists.newArrayList(DEFAULT_TEMPLATE));
when(permissionTemplateDao.selectByName(any(DbSession.class), anyString())).thenReturn(DEFAULT_TEMPLATE);

underTest.createPermissionTemplate(DEFAULT_KEY, DEFAULT_DESC, null);
}
@@ -131,7 +132,7 @@ public class PermissionTemplateServiceTest {
@Test
public void should_reject_empty_name_on_creation() {
expected.expect(BadRequestException.class);
expected.expectMessage("Name can't be blank");
expected.expectMessage("The template name must not be blank");

underTest.createPermissionTemplate("", DEFAULT_DESC, null);
}
@@ -139,7 +140,7 @@ public class PermissionTemplateServiceTest {
@Test
public void should_reject_invalid_key_pattern_on_creation() {
expected.expect(BadRequestException.class);
expected.expectMessage("Invalid pattern: [azerty. Should be a valid Java regular expression.");
expected.expectMessage("The 'projectPattern' parameter must be a valid Java regular expression. '[azerty' was passed");

underTest.createPermissionTemplate(DEFAULT_KEY, DEFAULT_DESC, "[azerty");
}
@@ -227,27 +228,25 @@ public class PermissionTemplateServiceTest {

underTest.updatePermissionTemplate(1L, "new_name", "new_description", null);

verify(permissionTemplateDao).updatePermissionTemplate(1L, "new_name", "new_description", null);
verify(permissionTemplateDao).update(1L, "new_name", "new_description", null);
}

@Test
public void should_validate_template_name_on_update_if_applicable() {
expected.expect(BadRequestException.class);
expected.expectMessage("A template with that name already exists");
expected.expectMessage("A template with the name 'template2' already exists (case insensitive).");

PermissionTemplateDto template1 =
new PermissionTemplateDto().setId(1L).setName("template1").setDescription("template1");
PermissionTemplateDto template2 =
new PermissionTemplateDto().setId(2L).setName("template2").setDescription("template2");
when(permissionTemplateDao.selectAllPermissionTemplates()).thenReturn(Lists.newArrayList(template1, template2));
when(permissionTemplateDao.selectByName(any(DbSession.class), eq("template2"))).thenReturn(template2);

underTest.updatePermissionTemplate(1L, "template2", "template1", null);
underTest.updatePermissionTemplate(1L, "template2", "template2", null);
}

@Test
public void should_validate_template_key_pattern_on_update_if_applicable() {
expected.expect(BadRequestException.class);
expected.expectMessage("Invalid pattern: [azerty. Should be a valid Java regular expression.");
expected.expectMessage("The 'projectPattern' parameter must be a valid Java regular expression. '[azerty' was passed");

PermissionTemplateDto template1 = new PermissionTemplateDto().setId(1L).setName("template1").setDescription("template1");
when(permissionTemplateDao.selectAllPermissionTemplates()).thenReturn(Lists.newArrayList(template1));
@@ -265,7 +264,7 @@ public class PermissionTemplateServiceTest {

underTest.updatePermissionTemplate(1L, "template1", "new_description", null);

verify(permissionTemplateDao).updatePermissionTemplate(1L, "template1", "new_description", null);
verify(permissionTemplateDao).update(1L, "template1", "new_description", null);
}

@Test

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupToTemplateActionTest.java View File

@@ -218,7 +218,7 @@ public class AddGroupToTemplateActionTest {
}

private PermissionTemplateDto insertPermissionTemplate(PermissionTemplateDto permissionTemplate) {
return dbClient.permissionTemplateDao().insertPermissionTemplate(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern());
return dbClient.permissionTemplateDao().insert(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern());
}

private List<String> getGroupNamesInTemplateAndPermission(long templateId, String permission) {

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserToTemplateActionTest.java View File

@@ -186,7 +186,7 @@ public class AddUserToTemplateActionTest {
}

private PermissionTemplateDto insertPermissionTemplate(PermissionTemplateDto permissionTemplate) {
return dbClient.permissionTemplateDao().insertPermissionTemplate(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern());
return dbClient.permissionTemplateDao().insert(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern());
}

private List<String> getLoginsInTemplateAndPermission(long templateId, String permission) {

+ 173
- 0
server/sonar-server/src/test/java/org/sonar/server/permission/ws/CreateTemplateActionTest.java View File

@@ -0,0 +1,173 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.permission.ws;

import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.permission.PermissionTemplateDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.db.permission.PermissionTemplateTesting.newPermissionTemplateDto;
import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_DESCRIPTION;
import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_NAME;
import static org.sonar.server.permission.ws.Parameters.PARAM_TEMPLATE_PATTERN;
import static org.sonar.test.JsonAssert.assertJson;

public class CreateTemplateActionTest {

@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public ExpectedException expectedException = ExpectedException.none();

WsActionTester ws;
DbClient dbClient;
DbSession dbSession;
System2 system = mock(System2.class);

@Before
public void setUp() {
userSession.login().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
when(system.now()).thenReturn(1440512328743L);

dbClient = db.getDbClient();
dbSession = db.getSession();
ws = new WsActionTester(new CreateTemplateAction(dbClient, userSession, system));
}

@Test
public void create_full_permission_template() {
TestResponse result = newRequest("Finance", "Permissions for financially related projects", ".*\\.finance\\..*");

assertJson(result.getInput()).isSimilarTo(getClass().getResource("CreateTemplateActionTest/create_template.json"));
PermissionTemplateDto finance = dbClient.permissionTemplateDao().selectByName(dbSession, "Finance");
assertThat(finance.getName()).isEqualTo("Finance");
assertThat(finance.getDescription()).isEqualTo("Permissions for financially related projects");
assertThat(finance.getKeyPattern()).isEqualTo(".*\\.finance\\..*");
assertThat(finance.getKee()).isNotEmpty();
assertThat(finance.getCreatedAt().getTime()).isEqualTo(1440512328743L);
assertThat(finance.getUpdatedAt().getTime()).isEqualTo(1440512328743L);
}

@Test
public void create_minimalist_permission_template() {
newRequest("Finance", null, null);

PermissionTemplateDto finance = dbClient.permissionTemplateDao().selectByName(dbSession, "Finance");
assertThat(finance.getName()).isEqualTo("Finance");
assertThat(finance.getDescription()).isNullOrEmpty();
assertThat(finance.getKeyPattern()).isNullOrEmpty();
assertThat(finance.getKee()).isNotEmpty();
assertThat(finance.getCreatedAt().getTime()).isEqualTo(1440512328743L);
assertThat(finance.getUpdatedAt().getTime()).isEqualTo(1440512328743L);
}

@Test
public void fail_if_name_not_provided() {
expectedException.expect(IllegalArgumentException.class);

newRequest(null, null, null);
}

@Test
public void fail_if_name_empty() {
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The template name must not be blank");

newRequest("", null, null);
}

@Test
public void fail_if_regexp_if_not_valid() {
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The 'projectPattern' parameter must be a valid Java regular expression. '[azerty' was passed");

newRequest("Finance", null, "[azerty");
}

@Test
public void fail_if_name_already_exists_in_database_case_insensitive() {
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("A template with the name 'Finance' already exists (case insensitive).");
insertTemplate(newPermissionTemplateDto().setName("finance"));
commit();

newRequest("Finance", null, null);
}

@Test
public void fail_if_not_logged_in() {
expectedException.expect(UnauthorizedException.class);
userSession.anonymous();

newRequest("Finance", null, null);
}

@Test
public void fail_if_not_admin() {
expectedException.expect(ForbiddenException.class);
userSession.setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN);

newRequest("Finance", null, null);
}

private PermissionTemplateDto insertTemplate(PermissionTemplateDto template) {
return dbClient.permissionTemplateDao().insert(dbSession, template);
}

private void commit() {
dbSession.commit();
}

private TestResponse newRequest(@Nullable String name, @Nullable String description, @Nullable String projectPattern) {
TestRequest request = ws.newRequest();
if (name != null) {
request.setParam(PARAM_TEMPLATE_NAME, name);
}
if (description != null) {
request.setParam(PARAM_TEMPLATE_DESCRIPTION, description);
}
if (projectPattern != null) {
request.setParam(PARAM_TEMPLATE_PATTERN, projectPattern);
}

return request.execute();
}
}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/permission/ws/PermissionsWsModuleTest.java View File

@@ -30,6 +30,6 @@ public class PermissionsWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new PermissionsWsModule().configure(container);
assertThat(container.size()).isEqualTo(18);
assertThat(container.size()).isEqualTo(19);
}
}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupFromTemplateActionTest.java View File

@@ -218,7 +218,7 @@ public class RemoveGroupFromTemplateActionTest {
}

private PermissionTemplateDto insertPermissionTemplate(PermissionTemplateDto permissionTemplate) {
return dbClient.permissionTemplateDao().insertPermissionTemplate(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern());
return dbClient.permissionTemplateDao().insert(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern());
}

private void addGroupToPermissionTemplate(long permissionTemplateId, @Nullable Long groupId, String permission) {

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserFromTemplateActionTest.java View File

@@ -210,7 +210,7 @@ public class RemoveUserFromTemplateActionTest {
}

private PermissionTemplateDto insertPermissionTemplate(PermissionTemplateDto permissionTemplate) {
return dbClient.permissionTemplateDao().insertPermissionTemplate(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern());
return dbClient.permissionTemplateDao().insert(permissionTemplate.getName(), permissionTemplate.getDescription(), permissionTemplate.getKeyPattern());
}

private List<String> getLoginsInTemplateAndPermission(long templateId, String permission) {

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPermissionTemplatesTest.java View File

@@ -79,7 +79,7 @@ public class RegisterPermissionTemplatesTest {

when(loadedTemplateDao.countByTypeAndKey(LoadedTemplateDto.PERMISSION_TEMPLATE_TYPE, PermissionTemplateDto.DEFAULT.getKee()))
.thenReturn(0);
when(permissionTemplateDao.insertPermissionTemplate(PermissionTemplateDto.DEFAULT.getName(), PermissionTemplateDto.DEFAULT.getDescription(), null))
when(permissionTemplateDao.insert(PermissionTemplateDto.DEFAULT.getName(), PermissionTemplateDto.DEFAULT.getDescription(), null))
.thenReturn(permissionTemplate);
when(groupDao.selectByName(any(DbSession.class), eq(DefaultGroups.ADMINISTRATORS))).thenReturn(new GroupDto().setId(1L));
when(groupDao.selectByName(any(DbSession.class), eq(DefaultGroups.USERS))).thenReturn(new GroupDto().setId(2L));
@@ -88,7 +88,7 @@ public class RegisterPermissionTemplatesTest {
initializer.start();

verify(loadedTemplateDao).insert(argThat(Matches.template(expectedTemplate)));
verify(permissionTemplateDao).insertPermissionTemplate(PermissionTemplateDto.DEFAULT.getName(), PermissionTemplateDto.DEFAULT.getDescription(), null);
verify(permissionTemplateDao).insert(PermissionTemplateDto.DEFAULT.getName(), PermissionTemplateDto.DEFAULT.getDescription(), null);
verify(permissionTemplateDao).insertGroupPermission(1L, 1L, UserRole.ADMIN);
verify(permissionTemplateDao).insertGroupPermission(1L, 1L, UserRole.ISSUE_ADMIN);
verify(permissionTemplateDao).insertGroupPermission(1L, null, UserRole.USER);

+ 8
- 0
server/sonar-server/src/test/resources/org/sonar/server/permission/ws/CreateTemplateActionTest/create_template.json View File

@@ -0,0 +1,8 @@
{
"permissionTemplate": {
"name": "Finance",
"description": "Permissions for financially related projects",
"createdAt": "2015-08-25T16:18:48+0200",
"updatedAt": "2015-08-25T16:18:48+0200"
}
}

+ 40
- 16
sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateDao.java View File

@@ -21,17 +21,16 @@
package org.sonar.db.permission;

import com.google.common.annotations.VisibleForTesting;
import java.text.Normalizer;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.sonar.api.security.DefaultGroups;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.Uuids;
import org.sonar.db.Dao;
import org.sonar.db.DbSession;
import org.sonar.db.MyBatis;
@@ -181,22 +180,33 @@ public class PermissionTemplateDao implements Dao {
}
}

public PermissionTemplateDto insertPermissionTemplate(String templateName, @Nullable String description, @Nullable String keyPattern) {
/**
* @deprecated since 5.2 use {@link #insert(DbSession, PermissionTemplateDto)}
*/
@Deprecated
public PermissionTemplateDto insert(String templateName, @Nullable String description, @Nullable String projectPattern) {
Date creationDate = now();

PermissionTemplateDto permissionTemplate = new PermissionTemplateDto()
.setName(templateName)
.setKee(generateTemplateKee(templateName, creationDate))
.setKee(generateTemplateKey(templateName))
.setDescription(description)
.setKeyPattern(keyPattern)
.setKeyPattern(projectPattern)
.setCreatedAt(creationDate)
.setUpdatedAt(creationDate);
SqlSession session = myBatis.openSession(false);

DbSession session = myBatis.openSession(false);
try {
mapper(session).insert(permissionTemplate);
session.commit();
return insert(session, permissionTemplate);
} finally {
MyBatis.closeQuietly(session);
}
}

public PermissionTemplateDto insert(DbSession session, PermissionTemplateDto permissionTemplate) {
mapper(session).insert(permissionTemplate);
session.commit();

return permissionTemplate;
}

@@ -213,22 +223,31 @@ public class PermissionTemplateDao implements Dao {
}
}

public void updatePermissionTemplate(Long templateId, String templateName, @Nullable String description, @Nullable String keyPattern) {
/**
* @deprecated since 5.2 use {@link #update(DbSession, PermissionTemplateDto)}
*/
@Deprecated
public void update(Long templateId, String templateName, @Nullable String description, @Nullable String projectPattern) {
PermissionTemplateDto permissionTemplate = new PermissionTemplateDto()
.setId(templateId)
.setName(templateName)
.setDescription(description)
.setKeyPattern(keyPattern)
.setKeyPattern(projectPattern)
.setUpdatedAt(now());
SqlSession session = myBatis.openSession(false);

DbSession session = myBatis.openSession(false);
try {
mapper(session).update(permissionTemplate);
session.commit();
update(session, permissionTemplate);
} finally {
MyBatis.closeQuietly(session);
}
}

public void update(DbSession session, PermissionTemplateDto permissionTemplate) {
mapper(session).update(permissionTemplate);
session.commit();
}

/**
* @deprecated since 5.2 {@link #insertUserPermission(DbSession, Long, Long, String)}
*/
@@ -334,6 +353,11 @@ public class PermissionTemplateDao implements Dao {
return templateWithPermissions;
}

public PermissionTemplateDto selectByName(DbSession dbSession, String templateName) {
char wildcard = '%';
return mapper(dbSession).selectByName(wildcard + templateName.toUpperCase() + wildcard);
}

/**
* Remove a group from all templates (used when removing a group)
*/
@@ -341,12 +365,12 @@ public class PermissionTemplateDao implements Dao {
session.getMapper(PermissionTemplateMapper.class).deleteByGroupId(groupId);
}

private String generateTemplateKee(String name, Date timeStamp) {
private String generateTemplateKey(String name) {
if (PermissionTemplateDto.DEFAULT.getName().equals(name)) {
return PermissionTemplateDto.DEFAULT.getKee();
}
String normalizedName = Normalizer.normalize(name, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "").replace(" ", "_");
return normalizedName.toLowerCase() + "_" + DateFormatUtils.format(timeStamp, "yyyyMMdd_HHmmss");
return Uuids.create();
}

private Date now() {

+ 2
- 0
sonar-db/src/main/java/org/sonar/db/permission/PermissionTemplateMapper.java View File

@@ -59,6 +59,8 @@ public interface PermissionTemplateMapper {

List<UserWithPermissionDto> selectUsers(Map<String, Object> params, RowBounds rowBounds);

PermissionTemplateDto selectByName(String name);

int countUsers(Map<String, Object> params);

int countGroups(Map<String, Object> parameters);

+ 15
- 2
sonar-db/src/main/resources/org/sonar/db/permission/PermissionTemplateMapper.xml View File

@@ -166,15 +166,28 @@
</where>
</select>

<sql id="templateColumns">
id, name, kee, description, key_pattern AS keyPattern, created_at AS createdAt, updated_at AS updatedAt
</sql>

<select id="selectByKey" parameterType="String" resultType="PermissionTemplate">
SELECT id, name, kee, description, key_pattern AS keyPattern, created_at AS createdAt, updated_at AS updatedAt
SELECT
<include refid="templateColumns"/>
FROM permission_templates
WHERE kee = #{kee}
</select>

<select id="selectAllPermissionTemplates" resultType="PermissionTemplate">
SELECT id, name, kee, description, key_pattern AS keyPattern, created_at AS createdAt, updated_at AS updatedAt
SELECT
<include refid="templateColumns"/>
FROM permission_templates
</select>

<select id="selectByName" parameterType="String" resultType="PermissionTemplate">
SELECT
<include refid="templateColumns"/>
FROM permission_templates
WHERE UPPER(name) LIKE #{templateName} ESCAPE '/'
</select>

<select id="selectTemplateUsersPermissions" parameterType="String" resultMap="fullPermissionsTemplateResult">

+ 4
- 18
sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateDaoTest.java View File

@@ -58,25 +58,11 @@ public class PermissionTemplateDaoTest {
Date now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2013-01-02 01:04:05");
when(system.now()).thenReturn(now.getTime());

PermissionTemplateDto permissionTemplate = underTest.insertPermissionTemplate("my template", "my description", "myregexp");
PermissionTemplateDto permissionTemplate = underTest.insert("my template", "my description", "myregexp");
assertThat(permissionTemplate).isNotNull();
assertThat(permissionTemplate.getId()).isEqualTo(1L);

dbTester.assertDbUnitTable(getClass(), "createPermissionTemplate-result.xml", "permission_templates", "id", "name", "kee", "description");
}

@Test
public void should_normalize_kee_on_template_creation() throws ParseException {
dbTester.prepareDbUnit(getClass(), "createNonAsciiPermissionTemplate.xml");

Date now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2013-01-02 01:04:05");
when(system.now()).thenReturn(now.getTime());

PermissionTemplateDto permissionTemplate = underTest.insertPermissionTemplate("Môü Gnô Gnèçàß", "my description", null);
assertThat(permissionTemplate).isNotNull();
assertThat(permissionTemplate.getId()).isEqualTo(1L);

dbTester.assertDbUnitTable(getClass(), "createNonAsciiPermissionTemplate-result.xml", "permission_templates", "id", "name", "kee", "description");
dbTester.assertDbUnitTable(getClass(), "createPermissionTemplate-result.xml", "permission_templates", "id", "name", "description");
}

@Test
@@ -92,7 +78,7 @@ public class PermissionTemplateDaoTest {
when(myBatis.openSession(false)).thenReturn(session);

underTest = new PermissionTemplateDao(myBatis, system);
PermissionTemplateDto permissionTemplate = underTest.insertPermissionTemplate(PermissionTemplateDto.DEFAULT.getName(), null, null);
PermissionTemplateDto permissionTemplate = underTest.insert(PermissionTemplateDto.DEFAULT.getName(), null, null);

verify(mapper).insert(permissionTemplate);
verify(session).commit();
@@ -164,7 +150,7 @@ public class PermissionTemplateDaoTest {
public void should_update_permission_template() {
dbTester.prepareDbUnit(getClass(), "updatePermissionTemplate.xml");

underTest.updatePermissionTemplate(1L, "new_name", "new_description", "new_regexp");
underTest.update(1L, "new_name", "new_description", "new_regexp");

dbTester.assertDbUnitTable(getClass(), "updatePermissionTemplate-result.xml", "permission_templates", "id", "name", "kee", "description");
}

+ 4
- 3
sonar-db/src/test/java/org/sonar/db/permission/PermissionTemplateTesting.java View File

@@ -21,17 +21,18 @@
package org.sonar.db.permission;

import java.util.Date;
import org.sonar.api.utils.internal.Uuids;

import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.apache.commons.lang.RandomStringUtils.randomAscii;
import static org.apache.commons.lang.math.RandomUtils.nextLong;

public class PermissionTemplateTesting {
public static PermissionTemplateDto newPermissionTemplateDto() {
return new PermissionTemplateDto()
.setName(randomAlphanumeric(60))
.setDescription(randomAscii(500))
.setCreatedAt(new Date(nextLong()))
.setUpdatedAt(new Date(nextLong()));
.setKee(Uuids.create())
.setCreatedAt(new Date())
.setUpdatedAt(new Date());
}
}

+ 1799
- 2
sonar-ws/src/main/gen-java/org/sonarqube/ws/Permissions.java
File diff suppressed because it is too large
View File


+ 15
- 0
sonar-ws/src/main/protobuf/ws-permissions.proto View File

@@ -81,3 +81,18 @@ message SearchProjectPermissionsResponse {
repeated Permission permissions = 2;
optional sonarqube.ws.commons.Paging paging = 3;
}

message PermissionTemplate {
optional string key = 1;
optional string name = 2;
optional string description = 3;
optional string projectPattern = 4;
// ex: 2015-08-25T16:18:48+0200
optional string createdAt = 5;
// ex: 2015-08-25T16:18:48+0200
optional string updatedAt = 6;
}

message CreatePermissionTemplateResponse {
optional PermissionTemplate permissionTemplate = 1;
}

Loading…
Cancel
Save