@@ -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)); | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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>" + |
@@ -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)); | |||
} | |||
} |
@@ -41,6 +41,7 @@ public class PermissionsWsModule extends Module { | |||
AddUserToTemplateAction.class, | |||
RemoveUserFromTemplateAction.class, | |||
AddGroupToTemplateAction.class, | |||
RemoveGroupFromTemplateAction.class); | |||
RemoveGroupFromTemplateAction.class, | |||
CreateTemplateAction.class); | |||
} | |||
} |
@@ -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); |
@@ -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" | |||
} | |||
} |
@@ -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(); |
@@ -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 |
@@ -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) { |
@@ -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) { |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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) { |
@@ -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) { |
@@ -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); |
@@ -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" | |||
} | |||
} |
@@ -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() { |
@@ -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); |
@@ -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"> |
@@ -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"); | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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; | |||
} |