Skip to content
This repository has been archived by the owner on Mar 31, 2022. It is now read-only.

Commit

Permalink
User substitution #30
Browse files Browse the repository at this point in the history
  • Loading branch information
dtaimanov committed Sep 13, 2021
1 parent 8a0a003 commit 0111488
Show file tree
Hide file tree
Showing 16 changed files with 987 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public Object getParameterValue(String paramName) {
if (paramName.startsWith(CURRENT_USER_PREFIX)) {
String attrName = paramName.substring(CURRENT_USER_PREFIX.length());

UserDetails user = currentAuthentication.getUser();
UserDetails user = currentAuthentication.getCurrentOrSubstitutedUser();
BeanInfo info;
try {
info = Introspector.getBeanInfo(user.getClass());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright 2021 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.jmix.securitydata.entity;

import io.jmix.core.annotation.DeletedBy;
import io.jmix.core.annotation.DeletedDate;
import io.jmix.core.entity.annotation.JmixGeneratedValue;
import io.jmix.core.metamodel.annotation.JmixEntity;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;

import javax.persistence.*;
import java.util.Date;
import java.util.UUID;

@JmixEntity(name = "sec_UserSubstitution")
@Table(name = "SEC_USER_SUBSTITUTION")
@Entity(name = "sec_UserSubstitution")
public class UserSubstitution {
@JmixGeneratedValue
@Column(name = "ID", nullable = false)
@Id
private UUID id;

@Version
@Column(name = "VERSION", nullable = false)
private Integer version;

@CreatedDate
@Column(name = "CREATE_TS")
private Date createTs;

@CreatedBy
@Column(name = "CREATED_BY", length = 50)
private String createdBy;

@LastModifiedDate
@Column(name = "UPDATE_TS")
private Date updateTs;

@LastModifiedBy
@Column(name = "UPDATED_BY", length = 50)
private String updatedBy;

@DeletedDate
@Column(name = "DELETE_TS")
private Date deleteTs;

@DeletedBy
@Column(name = "DELETED_BY", length = 50)
protected String deletedBy;

@Column(name = "USER_NAME")
private String userName;

@Column(name = "SUBSTITUTED_USER_NAME")
private String substitutedUserName;

@Column(name = "START_DATE")
@Temporal(TemporalType.DATE)
private Date startDate;

@Temporal(TemporalType.DATE)
@Column(name = "END_DATE")
private Date endDate;

public void setEndDate(Date endDate) {
this.endDate = endDate;
}

public Date getEndDate() {
return endDate;
}

public Date getStartDate() {
return startDate;
}

public void setStartDate(Date startDate) {
this.startDate = startDate;
}

public String getSubstitutedUserName() {
return substitutedUserName;
}

public void setSubstitutedUserName(String substitutedUserName) {
this.substitutedUserName = substitutedUserName;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public UUID getId() {
return id;
}

public void setId(UUID id) {
this.id = id;
}

public Integer getVersion() {
return version;
}

public void setVersion(Integer version) {
this.version = version;
}

public Date getCreateTs() {
return createTs;
}

public void setCreateTs(Date createTs) {
this.createTs = createTs;
}

public String getCreatedBy() {
return createdBy;
}

public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}

public Date getUpdateTs() {
return updateTs;
}

public void setUpdateTs(Date updateTs) {
this.updateTs = updateTs;
}

public String getUpdatedBy() {
return updatedBy;
}

public void setUpdatedBy(String updatedBy) {
this.updatedBy = updatedBy;
}

public Date getDeleteTs() {
return deleteTs;
}

public void setDeleteTs(Date deleteTs) {
this.deleteTs = deleteTs;
}

public String getDeletedBy() {
return deletedBy;
}

public void setDeletedBy(String deletedBy) {
this.deletedBy = deletedBy;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright 2021 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.jmix.securitydata.impl.substitution;

import io.jmix.core.TimeSource;
import io.jmix.core.UnconstrainedDataManager;
import io.jmix.core.security.AccessDeniedException;
import io.jmix.core.security.CurrentAuthentication;
import io.jmix.core.security.SecurityContextHelper;
import io.jmix.core.security.UserRepository;
import io.jmix.core.security.event.UserSubstitutedEvent;
import io.jmix.core.security.impl.SubstitutedUserAuthenticationToken;
import io.jmix.securitydata.entity.UserSubstitution;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

@Component("sec_UserSubstitutionManager")
public class UserSubstitutionManager {

@Autowired
private UnconstrainedDataManager dataManager;
@Autowired
private UserRepository userRepository;
@Autowired
private CurrentAuthentication currentAuthentication;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private TimeSource timeSource;
@Autowired
protected ApplicationEventPublisher eventPublisher;

/**
* @return users which can be substituted by current authenticated user
*/
public List<UserDetails> getCurrentSubstitutedUsers() {
return getSubstitutedUsers(currentAuthentication.getUser().getUsername());
}


/**
* @return users which can be substituted by user with specified {@code userName}
*/
public List<UserDetails> getSubstitutedUsers(String userName) {

List<UserSubstitution> userSubstitutions = dataManager.load(UserSubstitution.class)
.query("e.userName=:userName " +
"and (e.startDate is null or e.startDate<=:currentDate) " +
"and (e.endDate is null or e.endDate>=:currentDate)")
.parameter("currentDate", timeSource.currentTimestamp())
.parameter("userName", userName)
.list();
List<UserDetails> result = new LinkedList<>();
for (UserSubstitution substitution : userSubstitutions) {
result.add(userRepository.loadUserByUsername(substitution.getSubstitutedUserName()));
}
return result;
}

/**
* Check {@link UserSubstitution} records and performs user substitution
*
* @throws AccessDeniedException if current user isn't allowed to substitute user with specified name
*/
public void substituteUser(String substitutedUserName) {

if (!canSubstitute(currentAuthentication.getUser().getUsername(), substitutedUserName)) {
throw new AccessDeniedException("user_substitution",
String.format("User '%s' cannot substitute '%s'",
currentAuthentication.getUser().getUsername(),
substitutedUserName));
}

SubstitutedUserAuthenticationToken authentication = (SubstitutedUserAuthenticationToken) authenticationManager.authenticate(
new SubstitutedUserAuthenticationToken(Objects.requireNonNull(currentAuthentication.getAuthentication()),
substitutedUserName));

SecurityContextHelper.setAuthentication(authentication);
eventPublisher.publishEvent(new UserSubstitutedEvent((UserDetails) authentication.getPrincipal(), (UserDetails) authentication.getSubstitutedPrincipal()));
}

protected boolean canSubstitute(String userName, String substitutedUserName) {
return getSubstitutedUsers(userName).stream().anyMatch(userDetails -> userDetails.getUsername().equals(substitutedUserName));
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,32 @@
</column>
</createTable>
</changeSet>

<changeSet id="5" author="security-data">
<createTable tableName="SEC_USER_SUBSTITUTION">
<column name="ID" type="${uuid.type}">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="VERSION" type="int" defaultValue="1">
<constraints nullable="false"/>
</column>
<column name="CREATE_TS" type="datetime"/>
<column name="CREATED_BY" type="varchar(50)"/>
<column name="UPDATE_TS" type="datetime"/>
<column name="UPDATED_BY" type="varchar(50)"/>
<column name="DELETE_TS" type="datetime"/>
<column name="DELETED_BY" type="varchar(50)"/>

<column name="USER_NAME" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="SUBSTITUTED_USER_NAME" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="START_DATE" type="datetime"/>
<column name="END_DATE" type="datetime"/>
</createTable>

</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ io.jmix.securitydata.entity/RowLevelPolicyEntity.type=Type
io.jmix.securitydata.entity/RowLevelPolicyEntity.action=Action
io.jmix.securitydata.entity/RowLevelPolicyEntity.role=Role
io.jmix.securitydata.entity/ResourcePolicyEntity=Resource policy
io.jmix.securitydata.entity/RowLevelPolicyEntity.entityName=Entity name
io.jmix.securitydata.entity/RowLevelPolicyEntity.entityName=Entity name
io.jmix.securitydata.entity/UserSubstitution=User substitution
io.jmix.securitydata.entity/UserSubstitution.userName=User name
io.jmix.securitydata.entity/UserSubstitution.substitutedUserName=Substituted user name
io.jmix.securitydata.entity/UserSubstitution.startDate=Start date
io.jmix.securitydata.entity/UserSubstitution.endDate=End date

0 comments on commit 0111488

Please sign in to comment.