Commit 74b36c0b authored by Matija Obreza's avatar Matija Obreza
Browse files

Migration of data to new app-blocks:1.3 for ACL

- Users, OAuthClient are AclSid
- Roles are AclSid
- DB: Old permissions and object ownership migrated to new schema
- Permission editor
- Services cleanup
parent 9bcbdcf0
...@@ -20,6 +20,7 @@ import java.beans.Transient; ...@@ -20,6 +20,7 @@ import java.beans.Transient;
import javax.persistence.Cacheable; import javax.persistence.Cacheable;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
...@@ -34,6 +35,7 @@ import org.genesys2.server.servlet.controller.rest.serialization.UserSerializer; ...@@ -34,6 +35,7 @@ import org.genesys2.server.servlet.controller.rest.serialization.UserSerializer;
@Entity @Entity
@Table(name = "\"user\"") @Table(name = "\"user\"")
@JsonSerialize(using = UserSerializer.class) @JsonSerialize(using = UserSerializer.class)
@DiscriminatorValue(value = "1")
public class User extends BasicUser<UserRole> { public class User extends BasicUser<UserRole> {
private static final long serialVersionUID = 4564013753931115445L; private static final long serialVersionUID = 4564013753931115445L;
......
...@@ -46,9 +46,6 @@ public interface UserService extends BasicUserService<UserRole, User> { ...@@ -46,9 +46,6 @@ public interface UserService extends BasicUserService<UserRole, User> {
UserWrapper getWrappedById(long userId) throws UserException; UserWrapper getWrappedById(long userId) throws UserException;
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.id == #userId")
User updateData(long userId, String name, String email) throws UserException;
User getSystemUser(String string); User getSystemUser(String string);
Page<User> listUsers(Pageable pageable); Page<User> listUsers(Pageable pageable);
......
...@@ -174,6 +174,19 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem ...@@ -174,6 +174,19 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
userWrapper.setRoles(roles); userWrapper.setRoles(roles);
return userWrapper; return userWrapper;
} }
@Override
@Transactional
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.id == #user.id")
public User updateUser(User user, String email, String fullName) throws UserException {
if (!emailValidator.isValid(email)) {
LOG.warn("Invalid email provided: {}", email);
throw new UserException("Invalid email provided: " + email);
}
return super.updateUser(user, email, fullName);
}
protected void updateUser(User user) throws UserException { protected void updateUser(User user) throws UserException {
try { try {
...@@ -187,28 +200,6 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem ...@@ -187,28 +200,6 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
} }
} }
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.id == #userId")
@Transactional(readOnly = false, rollbackFor = NotUniqueUserException.class)
public User updateData(long userId, String name, String email) throws UserException {
final User user = userRepository.findOne(userId);
if (user == null) {
throw new UserException("No user with id=" + userId);
}
if (!emailValidator.isValid(email)) {
LOG.warn("Invalid email provided: {}", email);
throw new UserException("Invalid email provided: " + email);
}
if (!StringUtils.equals(email, user.getEmail()) && userRepository.findByEmail(email) != null) {
throw new NotUniqueUserException(new Throwable(), email);
}
user.setFullName(name);
user.setEmail(email);
userRepository.save(user);
return user;
}
@Override @Override
@Transactional @Transactional
......
...@@ -19,7 +19,6 @@ package org.genesys2.server.servlet.controller; ...@@ -19,7 +19,6 @@ package org.genesys2.server.servlet.controller;
import org.genesys.blocks.security.model.AclObjectIdentity; import org.genesys.blocks.security.model.AclObjectIdentity;
import org.genesys.blocks.security.service.CustomAclService; import org.genesys.blocks.security.service.CustomAclService;
import org.genesys2.server.model.UserRole; import org.genesys2.server.model.UserRole;
import org.genesys2.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
...@@ -38,9 +37,6 @@ public class AclEditController extends BaseController { ...@@ -38,9 +37,6 @@ public class AclEditController extends BaseController {
@Autowired @Autowired
private CustomAclService aclService; private CustomAclService aclService;
@Autowired
private UserService userService;
@RequestMapping("/{clazz}/{id}/permissions") @RequestMapping("/{clazz}/{id}/permissions")
public String permissions(ModelMap model, @PathVariable(value = "clazz") String className, @PathVariable("id") long id, public String permissions(ModelMap model, @PathVariable(value = "clazz") String className, @PathVariable("id") long id,
@RequestParam(value = "back", required = false) String backUrl) { @RequestParam(value = "back", required = false) String backUrl) {
......
...@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest; ...@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.genesys.blocks.security.NotUniqueUserException; import org.genesys.blocks.security.NotUniqueUserException;
import org.genesys.blocks.security.UserException;
import org.genesys.blocks.security.model.BasicUser.AccountType; import org.genesys.blocks.security.model.BasicUser.AccountType;
import org.genesys.blocks.security.service.PasswordPolicy.PasswordPolicyException; import org.genesys.blocks.security.service.PasswordPolicy.PasswordPolicyException;
import org.genesys2.server.model.UserRole; import org.genesys2.server.model.UserRole;
...@@ -263,10 +264,13 @@ public class UserProfileController extends BaseController { ...@@ -263,10 +264,13 @@ public class UserProfileController extends BaseController {
} }
try { try {
user = userService.updateUser(user, email, fullName); user = userService.updateUser(user, email, fullName);
} catch (NotUniqueUserException e) { } catch (NotUniqueUserException e) {
redirectAttributes.addFlashAttribute("emailError", "User with e-mail address " + e.getEmail() + " already exists"); redirectAttributes.addFlashAttribute("emailError", "User with e-mail address " + e.getEmail() + " already exists");
return "redirect:/profile/" + user.getUuid() + "/edit"; return "redirect:/profile/" + user.getUuid() + "/edit";
} catch (UserException e) {
redirectAttributes.addFlashAttribute("emailError", e.getMessage());
return "redirect:/profile/" + user.getUuid() + "/edit";
} }
if (StringUtils.isNotBlank(pwd1) || StringUtils.isNotBlank(pwd2)) { if (StringUtils.isNotBlank(pwd1) || StringUtils.isNotBlank(pwd2)) {
......
...@@ -174,6 +174,9 @@ public class UserProfileController extends BaseController { ...@@ -174,6 +174,9 @@ public class UserProfileController extends BaseController {
} catch (NotUniqueUserException e) { } catch (NotUniqueUserException e) {
redirectAttributes.addFlashAttribute("emailError", "User with e-mail address " + e.getEmail() + " already exists"); redirectAttributes.addFlashAttribute("emailError", "User with e-mail address " + e.getEmail() + " already exists");
return "redirect:" + URLBASE + user.getUuid() + "/edit"; return "redirect:" + URLBASE + user.getUuid() + "/edit";
} catch (UserException e) {
redirectAttributes.addFlashAttribute("emailError", e.getMessage());
return "redirect:" + URLBASE + user.getUuid() + "/edit";
} }
if (StringUtils.isNotBlank(pwd1)) { if (StringUtils.isNotBlank(pwd1)) {
......
...@@ -87,19 +87,19 @@ public class PermissionController extends RestController { ...@@ -87,19 +87,19 @@ public class PermissionController extends RestController {
} }
@RequestMapping(value = "/autocompleteuser", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(value = "/autocompleteuser", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody Map<Long, String> acUser(@RequestParam("term") String email) { public @ResponseBody Map<String, Long> acUser(@RequestParam("term") String email) {
final Map<Long, String> userIds = new HashMap<>(); final Map<String, Long> userIds = new HashMap<>();
for (User user : userService.autocompleteUser(email)) { for (User user : userService.autocompleteUser(email)) {
userIds.put(user.getId(), user.getEmail()); userIds.put(user.getEmail(), user.getId());
} }
return userIds; return userIds;
} }
@RequestMapping(value = "/autocomplete-oauth-client", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(value = "/autocomplete-oauth-client", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody Map<String, String> acOauthClient(@RequestParam("term") String title) { public @ResponseBody Map<String, Long> acOauthClient(@RequestParam("term") String title) {
final Map<String, String> oauthMap = new HashMap<>(); final Map<String, Long> oauthMap = new HashMap<>();
for (final OAuthClient client : clientDetailsService.autocompleteClients(title)) { for (final OAuthClient client : clientDetailsService.autocompleteClients(title)) {
oauthMap.put(client.getTitle(), client.getClientId()); oauthMap.put(client.getTitle(), client.getId());
} }
return oauthMap; return oauthMap;
} }
......
...@@ -35,7 +35,6 @@ import org.springframework.beans.factory.annotation.Value; ...@@ -35,7 +35,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
...@@ -100,13 +99,10 @@ public class UsersController extends RestController { ...@@ -100,13 +99,10 @@ public class UsersController extends RestController {
} }
try { try {
userService.updateData(user.getId(), userData.getName(), userData.getEmail()); userService.updateUser(user, userData.getEmail(), userData.getName());
} catch (NotUniqueUserException e) { } catch (NotUniqueUserException e) {
LOG.warn("User with e-mail address {} already exists", e.getEmail()); LOG.warn("User with e-mail address {} already exists", e.getEmail());
throw e; throw e;
} catch (UserException e) {
LOG.warn("E-mail address is incorrect");
throw e;
} }
if (StringUtils.isNotBlank(userData.getPwd1())) { if (StringUtils.isNotBlank(userData.getPwd1())) {
......
...@@ -858,3 +858,280 @@ databaseChangeLog: ...@@ -858,3 +858,280 @@ databaseChangeLog:
- sql: - sql:
comment: if article#slug=='user.reset-password-instructions' update to article#slug='user-reset-password-instructions' comment: if article#slug=='user.reset-password-instructions' update to article#slug='user-reset-password-instructions'
sql: update article set slug = 'user-reset-password-instructions' where slug = 'user.reset-password-instructions'; sql: update article set slug = 'user-reset-password-instructions' where slug = 'user.reset-password-instructions';
# User and OAuthClient extend AclSid
- changeSet:
id: 1509390480000-1
author: matijaobreza
comment: Migrate ACL to app-blocks-1.3-SNAPSHOT
changes:
- dropForeignKeyConstraint:
baseTableName: acl_entry
constraintName: FK_fhuoesmjef3mrv0gpja4shvcr
- dropForeignKeyConstraint:
baseTableName: acl_entry
constraintName: FK_i6xyfccd4y3wlwhgwpo4a9rm1
- dropForeignKeyConstraint:
baseTableName: acl_object_identity
constraintName: FK_nxv5we2ion9fwedbkge7syoc3
- dropUniqueConstraint:
tableName: acl_entry
constraintName: UK_gh5egfpe4gaqokya6s0567b0l
- renameTable:
oldTableName: acl_sid
newTableName: acl_sid_backup
- renameTable:
oldTableName: acl_entry
newTableName: acl_entry_backup
- createTable:
columns:
- column:
constraints:
nullable: true
name: type
type: INT
- column:
autoIncrement: true
constraints:
primaryKey: true
name: id
type: BIGINT
- column:
constraints:
nullable: false
name: active
type: BIT(1)
defaultValue: true
- column:
constraints:
nullable: false
name: version
type: INT
- column:
name: createdBy
type: BIGINT
- column:
name: createdDate
type: datetime(6)
- column:
name: lastModifiedBy
type: BIGINT
- column:
name: lastModifiedDate
type: datetime(6)
- column:
constraints:
nullable: false
name: principal
type: BIT(1)
- column:
constraints:
nullable: false
name: sid
type: VARCHAR(100)
tableName: acl_sid
- createTable:
columns:
- column:
autoIncrement: true
constraints:
primaryKey: true
name: id
type: BIGINT
- column:
constraints:
nullable: false
name: ace_order
type: BIGINT
- column:
constraints:
nullable: false
name: audit_failure
type: BIT(1)
- column:
constraints:
nullable: false
name: audit_success
type: BIT(1)
- column:
constraints:
nullable: false
name: granting
type: BIT(1)
- column:
constraints:
nullable: false
name: mask
type: BIGINT
- column:
constraints:
nullable: false
name: acl_object_identity
type: BIGINT
- column:
constraints:
nullable: false
name: sid
type: BIGINT
tableName: acl_entry
- addUniqueConstraint:
columnNames: acl_object_identity, ace_order
constraintName: UK_gh5egfpe4gaqokya6s0567b0l
tableName: acl_entry
# Migrate User data to AclSid
- sql:
comment: Migrate all users with their existing IDs to acl_sid
sql: >-
insert into acl_sid
(type, id, active, version, createdBy, createdDate, lastModifiedBy,
lastModifiedDate, principal, sid)
select
1, id, active, version, createdBy, createdDate, lastModifiedBy,
lastModifiedDate, true, uuid
from user;
- addForeignKeyConstraint:
baseColumnNames: id
baseTableName: user
constraintName: FK_8qtpnv06elxuryeuv1ac4ximm
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: id
referencedTableName: acl_sid
- dropColumn:
tableName: user
columnName: active
- dropColumn:
tableName: user
columnName: version
- dropColumn:
tableName: user
columnName: createdBy
- dropColumn:
tableName: user
columnName: createdDate
- dropColumn:
tableName: user
columnName: lastModifiedBy
- dropColumn:
tableName: user
columnName: lastModifiedDate
# Migrate OAuthClient data to AclSid
- sql:
comment: Migrate OAuthClient to AclSid, they get new IDs, but we can find them with clientId as acl_sid.sid
sql: >-
insert into acl_sid
(type, active, version, createdBy, createdDate, lastModifiedBy,
lastModifiedDate, principal, sid)
select
2, active, version, createdBy, createdDate, lastModifiedBy,
lastModifiedDate, true, clientId
from oauthclient;
- sql:
comment: Update OAuthClient#id values to their new IDs as per acl_sid
sql: >-
update oauthclient oa
join acl_sid sid on sid.sid=oa.clientId
set oa.id=sid.id
- addForeignKeyConstraint:
baseColumnNames: id
baseTableName: oauthclient
constraintName: FK_j9t6kj0254t7knyn57orqyaxk
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: id
referencedTableName: acl_sid
- dropColumn:
tableName: oauthclient
columnName: active
- dropColumn:
tableName: oauthclient
columnName: version
- dropColumn:
tableName: oauthclient
columnName: createdBy
- dropColumn:
tableName: oauthclient
columnName: createdDate
- dropColumn:
tableName: oauthclient
columnName: lastModifiedBy
- dropColumn:
tableName: oauthclient
columnName: lastModifiedDate
# Migrate acl_object_identity#owner_sid because these have changed
- sql:
comment: Migrate acl_object_identity#owner_sid because these have changed
sql: >-
update acl_object_identity oid
join acl_sid_backup oldsid on oldsid.id=oid.owner_sid
join acl_sid newsid on newsid.sid=oldsid.sid
set oid.owner_sid=newsid.id
- addForeignKeyConstraint:
baseColumnNames: owner_sid
baseTableName: acl_object_identity
constraintName: FK_nxv5we2ion9fwedbkge7syoc3
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: id
referencedTableName: acl_sid
# Migrate acl_sid_backup data for authorities (roles) -- i.e. everything that has not moved
- sql:
comment: Delete invalid authority records from acl_sid_backup (users and oauthclients)
sql: >-
delete oldsid from acl_sid_backup oldsid
inner join acl_sid newsid on newsid.sid=oldsid.sid
where oldsid.principal = 0;
- sql:
comment: Generate new acl_sid for ROLE entries (AclSid.type==0)
sql: >-
insert into acl_sid
(type, active, version, createdBy, createdDate, lastModifiedBy,
lastModifiedDate, principal, sid)
select
0, 1, 0, null, now(), null,
now(), false, sid
from acl_sid_backup where principal = 0;
# Migrate acl_entry data
- sql:
comment: Migrate acl_entry_backup data to acl_entry, using new acl_sid#id instead of the old one
sql: >-
insert into acl_entry
(ace_order, audit_failure, audit_success, granting, mask, acl_object_identity, sid)
select
e.ace_order, e.audit_failure, e.audit_success, e.granting, e.mask, e.acl_object_identity, newsid.id
from acl_entry_backup e
inner join acl_sid_backup oldsid on oldsid.id=e.sid
inner join acl_sid newsid on newsid.sid=oldsid.sid;
- dropTable:
tableName: acl_sid_backup
- dropTable:
tableName: acl_entry_backup
# Activate FK constraints
- addForeignKeyConstraint:
baseColumnNames: acl_object_identity
baseTableName: acl_entry
constraintName: FK_fhuoesmjef3mrv0gpja4shvcr
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: id
referencedTableName: acl_object_identity
- addForeignKeyConstraint:
baseColumnNames: sid
baseTableName: acl_entry
constraintName: FK_i6xyfccd4y3wlwhgwpo4a9rm1
deferrable: false
initiallyDeferred: false
onDelete: NO ACTION
onUpdate: NO ACTION
referencedColumnNames: id
referencedTableName: acl_sid
...@@ -15,7 +15,9 @@ ...@@ -15,7 +15,9 @@
<p> <p>
<spring:message code="acl.owner"/>: <spring:message code="acl.owner"/>:
<c:out value="${jspHelper.aclSidById(aclObjectIdentity.ownerSid.id).fullName}"/> <c:set var="sid" value="${jspHelper.aclSidById(aclObjectIdentity.ownerSid.id)}" />
<c:out value="${sid.fullName}"/>
<c:catch><a href="<c:url value='mailto:${sid.email}' />"><c:out value="${sid.email}" /></a></c:catch>
</p> </p>
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
...@@ -29,9 +31,11 @@ ...@@ -29,9 +31,11 @@
<tbody> <tbody>
<c:forEach items="${aclSids}" var="aclSid" varStatus="status"> <c:forEach items="${aclSids}" var="aclSid" varStatus="status">
<c:set var="sid" value="${jspHelper.aclSidById(aclSid.id)}" />
<tr class="${status.count % 2 == 0 ? 'even' : 'odd'}"> <tr class="${status.count % 2 == 0 ? 'even' : 'odd'}">
<td> <td>
<c:out value="${jspHelper.aclSidById(aclSid.id).fullName}"/> <c:out value="${sid.fullName}"/>
<c:catch><a href="<c:url value='mailto:${sid.email}' />"><c:out value="${sid.email}" /></a></c:catch>
</td> </td>
<input type="hidden" name="sid" class="aclSid" value="${aclSid.id}"/> <input type="hidden" name="sid" class="aclSid" value="${aclSid.id}"/>
......
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
**/ **/
package org.genesys2.tests.unit; package org.genesys2.tests.unit;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;