Skip to content

Commit

Permalink
Add an option to restrict which locales a user can edit
Browse files Browse the repository at this point in the history
  • Loading branch information
mensinda committed Mar 1, 2024
1 parent 5bde739 commit 8c88bdd
Show file tree
Hide file tree
Showing 31 changed files with 654 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void testDelete() {
String commonName = "Test Mojito";

userService.createUserWithRole(
username, password, Role.ROLE_USER, givenName, surname, commonName, false);
username, password, Role.ROLE_USER, givenName, surname, commonName, null, true, false);
User user = userRepository.findByUsername(username);
assertNotNull(user);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private User createTestUserUsingUserService(String username, String rolename) {
}
User user =
userService.createUserWithRole(
username, password, role, givenName, surname, commonName, false);
username, password, role, givenName, surname, commonName, null, true, false);
return user;
}
}
20 changes: 20 additions & 0 deletions restclient/src/main/java/com/box/l10n/mojito/rest/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ public class User {

private String commonName;

private boolean canTranslateAllLocales;

@JsonManagedReference Set<Authority> authorities = new HashSet<>();

@JsonManagedReference Set<UserLocale> userLocales = new HashSet<>();

public Long getId() {
return id;
}
Expand Down Expand Up @@ -90,4 +94,20 @@ public Set<Authority> getAuthorities() {
public void setAuthorities(Set<Authority> authorities) {
this.authorities = authorities;
}

public boolean getCanTranslateAllLocales() {
return canTranslateAllLocales;
}

public void setCanTranslateAllLocales(boolean canTranslateAllLocales) {
this.canTranslateAllLocales = canTranslateAllLocales;
}

public Set<UserLocale> getUserLocales() {
return userLocales;
}

public void setUserLocales(Set<UserLocale> userLocales) {
this.userLocales = userLocales;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.box.l10n.mojito.rest.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;

public class UserLocale {
@JsonBackReference private User user;

private Locale locale;

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public Locale getLocale() {
return locale;
}

public void setLocale(Locale locale) {
this.locale = locale;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public class User extends AuditableEntity implements Serializable {
@JsonView(View.IdAndName.class)
String commonName;

@Column(name = "can_translate_all_locales", nullable = false)
boolean canTranslateAllLocales = true;

/**
* Sets this flag if the user is created by a process that don't have all the information. Eg.
* pushing an asset for a branch with an owner or header base authentication. If the owner is not
Expand All @@ -66,6 +69,10 @@ public class User extends AuditableEntity implements Serializable {
@Column(name = "partially_created")
Boolean partiallyCreated = false;

@JsonManagedReference
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
Set<UserLocale> userLocales = new HashSet<>();

@JsonManagedReference
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
Set<Authority> authorities = new HashSet<>();
Expand Down Expand Up @@ -142,6 +149,22 @@ public void setCommonName(String commonName) {
this.commonName = commonName;
}

public boolean getCanTranslateAllLocales() {
return canTranslateAllLocales;
}

public void setCanTranslateAllLocales(boolean canTranslateAllLocales) {
this.canTranslateAllLocales = canTranslateAllLocales;
}

public Set<UserLocale> getUserLocales() {
return userLocales;
}

public void setUserLocales(Set<UserLocale> userLocales) {
this.userLocales = userLocales;
}

public Boolean getPartiallyCreated() {
return partiallyCreated;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.box.l10n.mojito.entity.security.user;

import com.box.l10n.mojito.entity.BaseEntity;
import com.box.l10n.mojito.entity.Locale;
import com.box.l10n.mojito.rest.View;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonView;
import javax.persistence.Entity;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.BatchSize;

@Entity
@Table(
name = "user_locale",
indexes = {
@Index(
name = "UK__USER_LOCALE__USER_ID__LOCALE_ID",
columnList = "user_id, locale_id",
unique = true)
})
@BatchSize(size = 1000)
public class UserLocale extends BaseEntity {

@ManyToOne
@JsonBackReference
@JoinColumn(
name = "user_id",
foreignKey = @ForeignKey(name = "FK__USER_LOCALE__USER__ID"),
nullable = false)
User user;

@JsonView(View.LocaleSummary.class)
@ManyToOne
@JoinColumn(
name = "locale_id",
foreignKey = @ForeignKey(name = "FK__USER_LOCALE__LOCALE__ID"),
nullable = false)
Locale locale;

public UserLocale() {}

public UserLocale(User user, Locale locale) {
this.user = user;
this.locale = locale;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public Locale getLocale() {
return locale;
}

public void setLocale(Locale locale) {
this.locale = locale;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.box.l10n.mojito.react;

import com.box.l10n.mojito.entity.security.user.Authority;
import com.box.l10n.mojito.entity.security.user.UserLocale;
import com.box.l10n.mojito.json.ObjectMapper;
import com.box.l10n.mojito.mustache.MustacheBaseContext;
import com.box.l10n.mojito.mustache.MustacheTemplateEngine;
Expand All @@ -14,6 +15,7 @@
import java.nio.charset.StandardCharsets;
import java.util.IllformedLocaleException;
import java.util.Locale;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -134,6 +136,12 @@ ReactUser getReactUser() {
reactUser.setGivenName(currentAuditor.getGivenName());
reactUser.setSurname(currentAuditor.getSurname());
reactUser.setCommonName(currentAuditor.getCommonName());
reactUser.setCanTranslateAllLocales(currentAuditor.getCanTranslateAllLocales());
reactUser.setUserLocales(
currentAuditor.getUserLocales().stream()
.map(UserLocale::getLocale)
.map(com.box.l10n.mojito.entity.Locale::getBcp47Tag)
.collect(Collectors.toList()));

Role role = Role.ROLE_USER;
Authority authority = authorityRepository.findByUser(currentAuditor);
Expand Down
19 changes: 19 additions & 0 deletions webapp/src/main/java/com/box/l10n/mojito/react/ReactUser.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.box.l10n.mojito.react;

import com.box.l10n.mojito.security.Role;
import java.util.List;
import org.springframework.stereotype.Component;

@Component
Expand All @@ -11,6 +12,8 @@ public class ReactUser {
String surname;
String commonName;
Role role;
boolean canTranslateAllLocales;
List<String> userLocales;

public String getUsername() {
return username;
Expand Down Expand Up @@ -51,4 +54,20 @@ public Role getRole() {
public void setRole(Role role) {
this.role = role;
}

public boolean getCanTranslateAllLocales() {
return canTranslateAllLocales;
}

public void setCanTranslateAllLocales(boolean canTranslateAllLocales) {
this.canTranslateAllLocales = canTranslateAllLocales;
}

public List<String> getUserLocales() {
return userLocales;
}

public void setUserLocales(List<String> userLocales) {
this.userLocales = userLocales;
}
}
13 changes: 13 additions & 0 deletions webapp/src/main/java/com/box/l10n/mojito/rest/security/UserWS.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
import static org.slf4j.LoggerFactory.getLogger;
import static org.springframework.data.jpa.domain.Specification.where;

import com.box.l10n.mojito.entity.Locale;
import com.box.l10n.mojito.entity.security.user.Authority;
import com.box.l10n.mojito.entity.security.user.User;
import com.box.l10n.mojito.entity.security.user.UserLocale;
import com.box.l10n.mojito.security.Role;
import com.box.l10n.mojito.service.security.user.UserRepository;
import com.box.l10n.mojito.service.security.user.UserService;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
Expand Down Expand Up @@ -98,6 +101,11 @@ public ResponseEntity<User> createUser(@RequestBody User user) {
user.getGivenName(),
user.getSurname(),
user.getCommonName(),
user.getUserLocales().stream()
.map(UserLocale::getLocale)
.map(Locale::getBcp47Tag)
.collect(Collectors.toSet()),
user.getCanTranslateAllLocales(),
false);

return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
Expand Down Expand Up @@ -151,6 +159,11 @@ public void updateUserByUserId(@PathVariable Long userId, @RequestBody User user
user.getGivenName(),
user.getSurname(),
user.getCommonName(),
user.getUserLocales().stream()
.map(UserLocale::getLocale)
.map(Locale::getBcp47Tag)
.collect(Collectors.toSet()),
user.getCanTranslateAllLocales(),
false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

import com.box.l10n.mojito.entity.Asset;
import com.box.l10n.mojito.entity.AssetTextUnit;
import com.box.l10n.mojito.entity.BaseEntity;
import com.box.l10n.mojito.entity.Locale;
import com.box.l10n.mojito.entity.PollableTask;
import com.box.l10n.mojito.entity.Repository;
import com.box.l10n.mojito.entity.TMTextUnitCurrentVariant;
import com.box.l10n.mojito.entity.TMTextUnitVariant;
import com.box.l10n.mojito.entity.security.user.User;
import com.box.l10n.mojito.entity.security.user.UserLocale;
import com.box.l10n.mojito.json.ObjectMapper;
import com.box.l10n.mojito.rest.View;
import com.box.l10n.mojito.security.AuditorAwareImpl;
import com.box.l10n.mojito.service.NormalizationUtils;
import com.box.l10n.mojito.service.asset.AssetPathNotFoundException;
import com.box.l10n.mojito.service.asset.AssetRepository;
Expand All @@ -20,6 +24,7 @@
import com.box.l10n.mojito.service.pollableTask.PollableFuture;
import com.box.l10n.mojito.service.repository.RepositoryNameNotFoundException;
import com.box.l10n.mojito.service.repository.RepositoryRepository;
import com.box.l10n.mojito.service.security.user.UserRepository;
import com.box.l10n.mojito.service.tm.TMService;
import com.box.l10n.mojito.service.tm.TMTextUnitCurrentVariantService;
import com.box.l10n.mojito.service.tm.TMTextUnitHistoryService;
Expand All @@ -37,12 +42,15 @@
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down Expand Up @@ -87,6 +95,10 @@ public class TextUnitWS {

@Autowired AssetRepository assetRepository;

@Autowired AuditorAwareImpl auditorAwareImpl;

@Autowired UserRepository userRepository;

/**
* Gets the TextUnits that matches the search parameters.
*
Expand Down Expand Up @@ -247,6 +259,8 @@ String emptyOrString(String string, SearchType searchType) {
@Transactional
@RequestMapping(method = RequestMethod.POST, value = "/api/textunits")
public TextUnitDTO addTextUnit(@RequestBody TextUnitDTO textUnitDTO) {
checkUserCanEditLocale(textUnitDTO.getLocaleId());

logger.debug("Add TextUnit");
textUnitDTO.setTarget(NormalizationUtils.normalize(textUnitDTO.getTarget()));
TMTextUnitCurrentVariant addTMTextUnitCurrentVariant =
Expand Down Expand Up @@ -467,4 +481,26 @@ public PollableTask saveGitBlameWithUsages(
gitBlameService.saveGitBlameWithUsages(gitBlameWithUsages);
return pollableFuture.getPollableTask();
}

private void checkUserCanEditLocale(Long localeId) {
// Fetch the User from the DB to ensure it is up to date
final Optional<String> username = auditorAwareImpl.getCurrentAuditor().map(User::getUsername);
if (username.isEmpty() || localeId == null) {
return;
}
final User user = userRepository.findByUsername(username.get());

// Check if the user is allowed to edit the locale
if (!user.getCanTranslateAllLocales()) {
boolean canEditLocale =
user.getUserLocales().stream()
.map(UserLocale::getLocale)
.map(BaseEntity::getId)
.anyMatch(x -> Objects.equals(x, localeId));
if (!canEditLocale) {
throw new AccessDeniedException(
"The user is not authorized to edit the locale with ID: " + localeId);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.box.l10n.mojito.service.security.user;

import com.box.l10n.mojito.entity.security.user.UserLocale;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource(exported = false)
public interface UserLocaleRepository
extends JpaRepository<UserLocale, Long>, JpaSpecificationExecutor<UserLocale> {}
Loading

0 comments on commit 8c88bdd

Please sign in to comment.