Commit d2ca50a6 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '184-oauthclient-as-acl-sid' into 'master'

OAuthClient as ACL SID

Closes #184

See merge request genesys-pgr/genesys-server!112
parents da73a733 74b36c0b
......@@ -135,7 +135,7 @@ module.exports = function(grunt) {
'bower_components/tinymce/tinymce.jquery.js', 'bower_components/tinymce/themes/modern/theme.js', 'bower_components/tinymce/plugins/link/plugin.js',
'bower_components/tinymce/plugins/autolink/plugin.js', 'bower_components/tinymce/plugins/code/plugin.js', 'bower_components/leaflet/dist/leaflet.js',
'bower_components/leaflet-locationfilter/src/locationfilter.js', 'bower_components/bootstrap-sass/assets/javascripts/bootstrap.js', 'bower_components/jquery-ui/jquery-ui.js',
'bower_components/jquery-ui/ui/autocomplete.js', 'bower_components/jstree/dist/jstree.min.js' ],
'bower_components/jquery-ui/ui/autocomplete.js', 'bower_components/jstree/dist/jstree.min.js', 'bower_components/jquery-serialize-object/jquery.serialize-object.js' ],
dest : '<%= app.dist1 %>/js/libraries.js',
},
app1 : {
......
......@@ -17,6 +17,7 @@
"fontawesome": "~4.4.0",
"highcharts": "~4.1.8",
"highmaps-beta": "highmaps#~1.0.1",
"jstree": "~3.2.1"
"jstree": "~3.2.1",
"jquery-serialize-object": "^2.5.0"
}
}
......@@ -20,6 +20,7 @@ import java.beans.Transient;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.Table;
......@@ -34,6 +35,7 @@ import org.genesys2.server.servlet.controller.rest.serialization.UserSerializer;
@Entity
@Table(name = "\"user\"")
@JsonSerialize(using = UserSerializer.class)
@DiscriminatorValue(value = "1")
public class User extends BasicUser<UserRole> {
private static final long serialVersionUID = 4564013753931115445L;
......
......@@ -46,9 +46,6 @@ public interface UserService extends BasicUserService<UserRole, User> {
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);
Page<User> listUsers(Pageable pageable);
......
......@@ -46,6 +46,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang.StringUtils;
import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.service.CustomAclService;
import org.genesys2.server.model.elastic.AccessionDetails;
import org.genesys2.server.model.genesys.Accession;
......@@ -121,7 +122,6 @@ import org.springframework.data.repository.query.Param;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
......@@ -1043,9 +1043,9 @@ public class GenesysServiceImpl implements GenesysService, DatasetService {
@Override
@PreAuthorize("isAuthenticated()")
public List<Metadata> listMyMetadata() {
final UserDetails user = SecurityContextUtil.getCurrentUser();
final List<Long> oids = aclService.listIdentitiesForSid(Metadata.class, user, BasePermission.WRITE);
LOG.info("Got {} elements for {}", oids.size(), user.getUsername());
final AclSid sid = SecurityContextUtil.getCurrentUser();
final List<Long> oids = aclService.listObjectIdentityIdsForSid(Metadata.class, sid, BasePermission.WRITE);
LOG.info("Got {} elements for {}", oids.size(), sid);
if (oids.size() == 0) {
return null;
}
......
......@@ -25,6 +25,7 @@ import java.util.Map;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang.StringUtils;
import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.service.CustomAclService;
import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.FaoInstitute;
......@@ -49,7 +50,6 @@ import org.springframework.data.domain.Sort.Direction;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -240,9 +240,9 @@ public class InstituteServiceImpl implements InstituteService {
@Override
@PreAuthorize("isAuthenticated()")
public List<FaoInstitute> listMyInstitutes(Sort sort) {
final UserDetails user = SecurityContextUtil.getCurrentUser();
final List<Long> oids = aclService.listIdentitiesForSid(FaoInstitute.class, user, BasePermission.WRITE);
LOG.info("Got {} elements for {}", oids.size(), user.getUsername());
final AclSid sid = SecurityContextUtil.getCurrentUser();
final List<Long> oids = aclService.listObjectIdentityIdsForSid(FaoInstitute.class, sid, BasePermission.WRITE);
LOG.info("Got {} elements for {}", oids.size(), sid);
if (oids.size() == 0) {
return null;
}
......
......@@ -23,6 +23,7 @@ import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.genesys.blocks.security.SecurityContextUtil;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.service.CustomAclService;
import org.genesys2.server.model.genesys.AccessionId;
import org.genesys2.server.model.genesys.Metadata;
......@@ -50,7 +51,6 @@ import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -273,9 +273,9 @@ public class TraitServiceImpl implements TraitService {
@Override
@PreAuthorize("isAuthenticated()")
public List<Method> listMyMethods() {
final UserDetails user = SecurityContextUtil.getCurrentUser();
final List<Long> oids = aclService.listIdentitiesForSid(Method.class, user, BasePermission.WRITE);
LOG.info("Got {} elements for {}", oids.size(), user.getUsername());
final AclSid sid = SecurityContextUtil.getCurrentUser();
final List<Long> oids = aclService.listObjectIdentityIdsForSid(Method.class, sid, BasePermission.WRITE);
LOG.info("Got {} elements for {}", oids.size(), sid);
if (oids.size() == 0) {
return null;
}
......
......@@ -174,6 +174,19 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
userWrapper.setRoles(roles);
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 {
try {
......@@ -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
@Transactional
......
......@@ -19,7 +19,6 @@ package org.genesys2.server.servlet.controller;
import org.genesys.blocks.security.model.AclObjectIdentity;
import org.genesys.blocks.security.service.CustomAclService;
import org.genesys2.server.model.UserRole;
import org.genesys2.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.prepost.PreAuthorize;
......@@ -38,9 +37,6 @@ public class AclEditController extends BaseController {
@Autowired
private CustomAclService aclService;
@Autowired
private UserService userService;
@RequestMapping("/{clazz}/{id}/permissions")
public String permissions(ModelMap model, @PathVariable(value = "clazz") String className, @PathVariable("id") long id,
@RequestParam(value = "back", required = false) String backUrl) {
......
/**
* Copyright 2015 Global Crop Diversity Trust
/*
* Copyright 2017 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -12,7 +12,7 @@
* 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 org.genesys2.server.servlet.controller;
......@@ -25,6 +25,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.service.CustomAclService;
import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.model.impl.User;
......@@ -45,6 +47,9 @@ public class JspHelper {
@Autowired
private UserService userService;
@Autowired
private CustomAclService aclService;
@Autowired
private GeoService geoService;
......@@ -77,6 +82,13 @@ public class JspHelper {
return userService.getUserByUuid(uuid);
}
public AclSid aclSidById(Long id) {
if (id == null) {
return null;
}
return aclService.getSid(id);
}
public Country getCountry(String iso3) {
return geoService.getCountry(iso3);
}
......
......@@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
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.service.PasswordPolicy.PasswordPolicyException;
import org.genesys2.server.model.UserRole;
......@@ -263,10 +264,13 @@ public class UserProfileController extends BaseController {
}
try {
user = userService.updateUser(user, email, fullName);
user = userService.updateUser(user, email, fullName);
} catch (NotUniqueUserException e) {
redirectAttributes.addFlashAttribute("emailError", "User with e-mail address " + e.getEmail() + " already exists");
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)) {
......
/**
* Copyright 2014 Global Crop Diversity Trust
/*
* Copyright 2017 Global Crop Diversity Trust
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -12,7 +12,7 @@
* 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 org.genesys2.server.servlet.controller.admin;
......@@ -23,11 +23,13 @@ import com.fasterxml.jackson.annotation.JsonView;
import org.genesys.blocks.model.JsonViews;
import org.genesys.blocks.oauth.model.AccessToken;
import org.genesys.blocks.oauth.model.OAuthClient;
import org.genesys.blocks.oauth.model.OAuthRole;
import org.genesys.blocks.oauth.model.RefreshToken;
import org.genesys.blocks.oauth.service.OAuthClientDetailsService;
import org.genesys.blocks.oauth.service.OAuthTokenStoreService;
import org.genesys2.server.servlet.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
......@@ -36,9 +38,10 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Allow administrators to manage OAuth API keys.
* Allow administrators to manage OAuth clients and keys.
*/
@Controller
@RequestMapping(OAuthManagementController.CONTROLLER_PATH)
......@@ -126,6 +129,8 @@ public class OAuthManagementController extends BaseController {
public String editClient(Model model, @PathVariable("id") String clientId) {
final OAuthClient client = clientDetailsService.getClient(clientId);
model.addAttribute("clientDetails", client);
model.addAttribute("ROLE_CLIENT", OAuthRole.CLIENT);
model.addAttribute("ROLE_TRUSTED_CLIENT", OAuthRole.TRUSTED_CLIENT);
return VIEW_PATH + "/edit";
}
......@@ -150,13 +155,14 @@ public class OAuthManagementController extends BaseController {
return "redirect:" + CONTROLLER_PATH + "/";
}
@RequestMapping(value = "/save-client", method = RequestMethod.POST, params = { "id", "version", "action-save" })
public String saveExistinClient(Model model, @RequestBody @JsonView(JsonViews.Protected.class) OAuthClient updates, @RequestParam("id") long id,
@RequestMapping(value = "/save-client", method = RequestMethod.POST, params = { "id", "version", "action-save" }, consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = {
MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody OAuthClient saveExistingClient(Model model, @RequestBody @JsonView(JsonViews.Protected.class) OAuthClient updates, @RequestParam("id") long id,
@RequestParam("version") int version) {
final OAuthClient clientDetails = clientDetailsService.updateClient(id, version, updates);
return "redirect:" + CONTROLLER_PATH + "/" + clientDetails.getId() + "/edit";
return clientDetails;
}
@PreAuthorize("hasRole('ADMINISTRATOR')")
......
......@@ -174,6 +174,9 @@ public class UserProfileController extends BaseController {
} catch (NotUniqueUserException e) {
redirectAttributes.addFlashAttribute("emailError", "User with e-mail address " + e.getEmail() + " already exists");
return "redirect:" + URLBASE + user.getUuid() + "/edit";
} catch (UserException e) {
redirectAttributes.addFlashAttribute("emailError", e.getMessage());
return "redirect:" + URLBASE + user.getUuid() + "/edit";
}
if (StringUtils.isNotBlank(pwd1)) {
......
......@@ -16,11 +16,13 @@
package org.genesys2.server.servlet.controller.rest;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import org.genesys.blocks.oauth.model.OAuthClient;
import org.genesys.blocks.oauth.service.OAuthClientDetailsService;
import org.genesys.blocks.security.model.AclObjectIdentity;
import org.genesys.blocks.security.model.AclSid;
import org.genesys.blocks.security.service.CustomAclService;
import org.genesys2.server.model.impl.User;
import org.genesys2.server.service.UserService;
......@@ -47,38 +49,58 @@ public class PermissionController extends RestController {
@Autowired
private UserService userService;
@Autowired
private OAuthClientDetailsService clientDetailsService;
@RequestMapping(value = "/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody Object addPermission(@RequestBody PermissionJson permissionJson) {
LOG.info("Adding permission {}", permissionJson);
final Map<Integer, Boolean> permissionMap = PermissionJsonUtil.createPermissionsMap(permissionJson);
if (permissionJson.isPrincipal()) {
final User user = userService.getUserByEmail(permissionJson.getUuid());
aclService.addPermissions(permissionJson.getOid(), permissionJson.getClazz(), user.getUuid(), permissionJson.isPrincipal(), permissionMap);
return JSON_OK;
} else {
aclService.addPermissions(permissionJson.getOid(), permissionJson.getClazz(), permissionJson.getUuid(), permissionJson.isPrincipal(),
permissionMap);
return JSON_OK;
if (permissionJson.getAuthority() != null) {
final AclSid sid = aclService.getAuthoritySid(permissionJson.getAuthority());
aclService.addPermissions(permissionJson.getOid(), permissionJson.getClazz(), sid, permissionMap);
} else if (permissionJson.getSid() != null) {
final AclSid sid = aclService.getSid(permissionJson.getSid());
aclService.addPermissions(permissionJson.getOid(), permissionJson.getClazz(), sid, permissionMap);
}
return JSON_OK;
}
@RequestMapping(value = "/update", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody Object updatePermissions(@RequestBody PermissionJson permissionJson) {
final Map<Integer, Boolean> permissionMap = PermissionJsonUtil.createPermissionsMap(permissionJson);
final AclObjectIdentity objectIdentity = aclService.ensureObjectIdentity(permissionJson.getClazz(), permissionJson.getOid());
aclService.updatePermission(objectIdentity, permissionJson.getUuid(), permissionMap);
if (permissionJson.getAuthority() != null) {
final AclSid sid = aclService.getAuthoritySid(permissionJson.getAuthority());
aclService.updatePermissions(objectIdentity, sid, permissionMap);
} else if (permissionJson.getSid() != null) {
final AclSid sid = aclService.getSid(permissionJson.getSid());
aclService.updatePermissions(objectIdentity, sid, permissionMap);
}
return JSON_OK;
}
@RequestMapping(value = "/autocompleteuser", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody List<String> acUser(@RequestParam("term") String email) {
List<String> userEmails = new ArrayList<String>();
public @ResponseBody Map<String, Long> acUser(@RequestParam("term") String email) {
final Map<String, Long> userIds = new HashMap<>();
for (User user : userService.autocompleteUser(email)) {
userEmails.add(user.getEmail());
userIds.put(user.getEmail(), user.getId());
}
return userIds;
}
@RequestMapping(value = "/autocomplete-oauth-client", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody Map<String, Long> acOauthClient(@RequestParam("term") String title) {
final Map<String, Long> oauthMap = new HashMap<>();
for (final OAuthClient client : clientDetailsService.autocompleteClients(title)) {
oauthMap.put(client.getTitle(), client.getId());
}
return userEmails;
return oauthMap;
}
}
......@@ -35,7 +35,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -100,13 +99,10 @@ public class UsersController extends RestController {
}
try {
userService.updateData(user.getId(), userData.getName(), userData.getEmail());
userService.updateUser(user, userData.getEmail(), userData.getName());
} catch (NotUniqueUserException e) {
LOG.warn("User with e-mail address {} already exists", e.getEmail());
throw e;
} catch (UserException e) {
LOG.warn("E-mail address is incorrect");
throw e;
}
if (StringUtils.isNotBlank(userData.getPwd1())) {
......
......@@ -17,10 +17,15 @@
package org.genesys2.server.servlet.model;
public class PermissionJson {
// sid
private Long sid;
private String authority;
// object identity
private long oid;
private String clazz;
private String uuid;
private boolean principal;
// permissions
private boolean create;
private boolean read;
private boolean write;
......@@ -29,9 +34,25 @@ public class PermissionJson {
@Override
public String toString() {
return "PJ oid=" + oid + " class=" + clazz + " uuid=" + uuid + " principal=" + principal;
return "PJ oid=" + oid + " class=" + clazz + " (sid=" + sid + " OR authority=" + authority + ")";
}
public void setSid(Long sid) {
this.sid = sid;
}
public Long getSid() {
return sid;
}
public void setAuthority(String authority) {
this.authority = authority;
}
public String getAuthority() {
return authority;
}
public long getOid() {
return oid;
}
......@@ -48,22 +69,6 @@ public class PermissionJson {
this.clazz = clazz;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public boolean isPrincipal() {
return principal;
}
public void setPrincipal(boolean principal) {
this.principal = principal;
}
public boolean isCreate() {
return create;
}
......
......@@ -62,25 +62,26 @@ public class OAuth2ServerConfig {
@Override
public void configure(final HttpSecurity http) throws Exception {
http
// Since we want the protected resources to be accessible in the UI as well we
// need session creation to be allowed (it's disabled by default in 2.0.6)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
http.requestMatchers().antMatchers("/oauth/**", "/api/**").and()
// no sessions
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER).and()
// no CSRF
.and().csrf().disable()
// cors
.cors()
// API info can be accessed anonymously
.and().authorizeRequests().antMatchers("/api/v0/info/version").permitAll()
.and().requestMatchers().antMatchers("/api/**", "/token")
.and().authorizeRequests().antMatchers("/api/**", "/token").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
.csrf().disable()
// And exception handling
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()).and()
.antMatcher("/oauth/**")
// authorize everthing on this path
.authorizeRequests().anyRequest().fullyAuthenticated().and()
// /api/**