Commit 06472395 authored by Administrator's avatar Administrator

Modifies REST API for a easier client integration

The REST API methods for entity creation and modification have been
simplified with more basic entities.

In addition, the UserResource class has been added so that it is
possible now for clients to use it for user authentication.
parent de68ebfe
......@@ -35,5 +35,6 @@ public class Administrator extends User implements Serializable {
*/
public Administrator(String login, String password) {
super(login, password);
this.role = "ADMIN";
}
}
......@@ -53,6 +53,7 @@ public class Owner extends User implements Serializable {
*/
public Owner(String login, String password) {
super(login, password);
this.role = "OWNER";
this.pets = new HashSet<>();
}
......
......@@ -33,6 +33,9 @@ public abstract class User implements Serializable {
@Column(length = 32, nullable = false)
protected String password;
@Column(name="role", insertable = false, updatable = false)
protected String role;
User() {}
/**
......@@ -78,6 +81,16 @@ public abstract class User implements Serializable {
this.login = login;
}
/**
* Returns the role of the user. This value is automatically set by JPA, as
* it is the value used as discriminator in the inheritance.
*
* @return the role of the user.
*/
public String getRole() {
return role;
}
/**
* Returns the MD5 of the user's password. Capital letters are used
......
......@@ -18,7 +18,8 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import es.uvigo.esei.xcs.domain.entities.Owner;
import es.uvigo.esei.xcs.domain.entities.Pet;
import es.uvigo.esei.xcs.rest.entity.OwnerEditionData;
import es.uvigo.esei.xcs.rest.entity.OwnerCreationData;
import es.uvigo.esei.xcs.service.OwnerService;
/**
......@@ -74,19 +75,20 @@ public class OwnerResource {
* Creates a new owner. This owner may include a list of pets, that will be
* also created.
*
* @param owner a new owner to be stored.
* @param ownerData a new owner to be stored.
* @return a {@code CREATED} response with the URI of the new owner in the
* {@code Location} header.
* @throws IllegalArgumentException if owner is {@code null} or if an owner
* with the same login already exists.
*/
@POST
public Response create(Owner owner) {
// Pets are serialized without owner.
assignOwnerToPets(owner);
public Response create(OwnerCreationData ownerData) {
if (ownerData == null) {
throw new IllegalArgumentException("ownerData can't be null");
}
try {
final Owner newOwner = this.service.create(owner);
final Owner newOwner = this.service.create(ownerData.toOwner());
final URI ownerUri = uriInfo.getAbsolutePathBuilder()
.path(newOwner.getLogin())
.build();
......@@ -101,15 +103,24 @@ public class OwnerResource {
* Updates an owner. This owner may include a list of pets, that will be
* also created or updated. If the owner does not exists it will be created.
*
* @param owner an owner to be updated.
* @param ownerData an owner to be updated.
* @return an empty {@code OK} response.
* @throws IllegalArgumentException if owner is {@code null}.
*/
@Path("{login}")
@PUT
public Response update(Owner owner) {
// Pets are serialized without owner.
assignOwnerToPets(owner);
public Response update(@PathParam("login") String login, OwnerEditionData ownerData) {
if (login == null) {
throw new IllegalArgumentException("login can't be null");
}
if (ownerData == null) {
throw new IllegalArgumentException("ownerData can't be null");
}
final Owner owner = this.service.get(login);
ownerData.assignData(owner);
this.service.update(owner);
return Response.ok().build();
......@@ -133,14 +144,4 @@ public class OwnerResource {
return Response.ok().build();
}
private static void assignOwnerToPets(Owner owner) {
if (owner == null)
throw new IllegalArgumentException("owner can't be null");
for (Pet pet : owner.getPets()) {
if (pet.getOwner() != owner)
pet.setOwner(owner);
}
}
}
......@@ -19,6 +19,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import es.uvigo.esei.xcs.domain.entities.Pet;
import es.uvigo.esei.xcs.rest.entity.PetData;
import es.uvigo.esei.xcs.service.PetService;
/**
......@@ -73,7 +74,7 @@ public class PetResource {
/**
* Creates a new pet owned by the current user.
*
* @param pet a new owner to be stored.
* @param petData a new owner to be stored.
* @return a {@code CREATED} response with the URI of the new pet in the
* {@code Location} header.
* @throws IllegalArgumentException if pet is {@code null} or if a pet with
......@@ -83,14 +84,15 @@ public class PetResource {
* thrown.
*/
@POST
public Response create(Pet pet) throws SecurityException {
if (pet == null)
public Response create(PetData petData) throws SecurityException {
if (petData == null)
throw new IllegalArgumentException("pet can't be null");
try {
final Pet newPet = this.service.create(pet);
final Pet pet = this.service.create(petData.toPet());
final URI petUri = uriInfo.getAbsolutePathBuilder()
.path(Integer.toString(newPet.getId()))
.path(Integer.toString(pet.getId()))
.build();
return Response.created(petUri).build();
......@@ -105,18 +107,23 @@ public class PetResource {
* Updates the information of a pet. If the pet is not stored, it will be
* created.
*
* @param pet a pet to be updated.
* @param id the identifier of the pet to be modified.
* @param petData a pet to be updated.
* @return an empty {@code OK} response.
* @throws IllegalArgumentException if pet is {@code null} of it has no
* owner.
* @throws SecurityException if the pet's owner is not the current user.
*/
@Path("{id}")
@PUT
public Response update(Pet pet) throws SecurityException {
if (pet == null)
public Response update(@PathParam("id") int id, PetData petData) throws SecurityException {
if (petData == null)
throw new IllegalArgumentException("pet can't be null");
try {
final Pet pet = this.service.get(id);
petData.assignData(pet);
this.service.update(pet);
return Response.ok().build();
......
package es.uvigo.esei.xcs.rest;
import javax.ejb.EJB;
import javax.ejb.EJBAccessException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import es.uvigo.esei.xcs.domain.entities.User;
import es.uvigo.esei.xcs.rest.entity.UserCredentials;
import es.uvigo.esei.xcs.service.UserService;
@Path("user")
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
@EJB
private UserService service;
@GET
public Response getCredentials() {
try {
final User currentUser = this.service.getCurrentUser();
return Response.ok(new UserCredentials(currentUser)).build();
} catch (EJBAccessException eae) {
throw new SecurityException(eae);
}
}
}
package es.uvigo.esei.xcs.rest.entity;
import java.io.Serializable;
import es.uvigo.esei.xcs.domain.entities.Owner;
public class OwnerCreationData implements Serializable {
private static final long serialVersionUID = 1L;
private String login;
private String password;
OwnerCreationData() {}
public OwnerCreationData(String login, String password) {
this.login = login;
this.password = password;
}
public String getLogin() {
return login;
}
public String getPassword() {
return password;
}
public Owner toOwner() {
return new Owner(this.login, this.password);
}
}
package es.uvigo.esei.xcs.rest.entity;
import java.io.Serializable;
import es.uvigo.esei.xcs.domain.entities.Owner;
public class OwnerEditionData implements Serializable {
private static final long serialVersionUID = 1L;
private String password;
OwnerEditionData() {}
public OwnerEditionData(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
public void assignData(Owner owner) {
owner.changePassword(this.password);
}
}
package es.uvigo.esei.xcs.rest.entity;
import java.io.Serializable;
import java.util.Date;
import es.uvigo.esei.xcs.domain.entities.AnimalType;
import es.uvigo.esei.xcs.domain.entities.Pet;
public class PetData implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private AnimalType animal;
private Date birth;
PetData() {}
public PetData(String name, AnimalType animal, Date birth) {
this.name = name;
this.animal = animal;
this.birth = birth;
}
public String getName() {
return name;
}
public AnimalType getAnimal() {
return animal;
}
public Date getBirth() {
return birth;
}
public Pet assignData(Pet pet) {
pet.setName(this.name);
pet.setAnimal(this.animal);
pet.setBirth(this.birth);
return pet;
}
public Pet toPet() {
return new Pet(this.name, this.animal, this.birth);
}
}
package es.uvigo.esei.xcs.rest.entity;
import java.io.Serializable;
import es.uvigo.esei.xcs.domain.entities.User;
public class UserCredentials implements Serializable {
private static final long serialVersionUID = 1L;
private String login;
private String role;
public UserCredentials(User user) {
this.login = user.getLogin();
this.role = user.getRole();
}
public String getLogin() {
return login;
}
public String getRole() {
return role;
}
}
......@@ -10,6 +10,18 @@
</servlet-mapping>
<!--Defining security constraint for type of roles available -->
<security-constraint>
<web-resource-collection>
<web-resource-name>user</web-resource-name>
<url-pattern>/api/user/*</url-pattern>
<http-method-omission>OPTIONS</http-method-omission>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
<role-name>OWNER</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>admin</web-resource-name>
......
package es.uvigo.esei.xcs.service;
import java.security.Principal;
import javax.annotation.security.RolesAllowed;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import es.uvigo.esei.xcs.domain.entities.User;
@Stateless
@RolesAllowed({"OWNER", "ADMIN"})
public class UserService {
@PersistenceContext
private EntityManager em;
@Inject
private Principal principal;
/**
* Returns the current user entity.
*
* @return the entity with the information of the current user.
*/
public User getCurrentUser() {
return this.em.find(User.class, this.principal.getName());
}
}
......@@ -63,8 +63,8 @@ public class PetServiceIntegrationTest {
.addPackage(Pet.class.getPackage())
.addAsResource("test-persistence.xml", "META-INF/persistence.xml")
.addAsWebInfResource("jboss-web.xml")
.addAsResource("arquillian.extension.persistence.properties")
.addAsResource("arquillian.extension.persistence.dbunit.properties")
.addAsResource("arquillian.extension.persistence.properties")
.addAsResource("arquillian.extension.persistence.dbunit.properties")
.addAsWebInfResource("beans.xml", "beans.xml");
}
......
......@@ -9,7 +9,9 @@ import org.junit.runners.Suite.SuiteClasses;
OwnerServiceIntegrationTest.class,
OwnerServiceIllegalAccessIntegrationTest.class,
PetServiceIntegrationTest.class,
PetServiceIllegalAccessIntegrationTest.class
PetServiceIllegalAccessIntegrationTest.class,
UserServiceIntegrationTest.class,
UserServiceIllegalAccessIntegrationTest.class
})
public class ServiceIntegrationTestSuite {
}
package es.uvigo.esei.xcs.service;
import static es.uvigo.esei.xcs.domain.entities.IsEqualToUser.equalToUser;
import static es.uvigo.esei.xcs.domain.entities.OwnersDataset.existentOwner;
import static es.uvigo.esei.xcs.domain.entities.UsersDataset.existentAdmin;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import javax.ejb.EJB;
import javax.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.persistence.CleanupUsingScript;
import org.jboss.arquillian.persistence.ShouldMatchDataSet;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
import es.uvigo.esei.xcs.domain.entities.Owner;
import es.uvigo.esei.xcs.domain.entities.OwnersDataset;
import es.uvigo.esei.xcs.domain.entities.Pet;
import es.uvigo.esei.xcs.domain.entities.User;
import es.uvigo.esei.xcs.domain.entities.UsersDataset;
import es.uvigo.esei.xcs.service.util.security.RoleCaller;
import es.uvigo.esei.xcs.service.util.security.TestPrincipal;
@RunWith(Arquillian.class)
@UsingDataSet("owners.xml")
@CleanupUsingScript({ "cleanup.sql", "cleanup-autoincrement.sql" })
public class UserServiceIllegalAccessIntegrationTest {
@Inject
private UserService facade;
@EJB(beanName = "owner-caller")
private RoleCaller asOwner;
@EJB(beanName = "admin-caller")
private RoleCaller asAdmin;
@Inject
private TestPrincipal principal;
@Deployment
public static Archive<?> createDeployment() {
return ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(UserService.class)
.addPackage(RoleCaller.class.getPackage())
.addPackage(Pet.class.getPackage())
.addAsResource("test-persistence.xml", "META-INF/persistence.xml")
.addAsWebInfResource("jboss-web.xml")
.addAsResource("arquillian.extension.persistence.properties")
.addAsResource("arquillian.extension.persistence.dbunit.properties")
.addAsWebInfResource("beans.xml", "beans.xml");
}
@Test
@ShouldMatchDataSet("owners.xml")
public void testGetOwnerCredentials() {
final Owner existentOwner = existentOwner();
principal.setName(existentOwner.getLogin());
final User actualUser = asOwner.call(() -> facade.getCurrentUser());
assertThat(actualUser, is(equalToUser(existentOwner)));
}
@Test
@ShouldMatchDataSet("owners.xml")
public void testGetAdminCredentials() {
final User existentAdmin = existentAdmin();
principal.setName(existentAdmin.getLogin());
final User actualUser = asAdmin.call(() -> facade.getCurrentUser());
assertThat(actualUser, is(equalToUser(existentAdmin)));
}
}
package es.uvigo.esei.xcs.service;
import javax.ejb.EJBAccessException;
import javax.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.persistence.CleanupUsingScript;
import org.jboss.arquillian.persistence.ShouldMatchDataSet;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
import es.uvigo.esei.xcs.domain.entities.Pet;
import es.uvigo.esei.xcs.service.util.security.RoleCaller;
@RunWith(Arquillian.class)
@UsingDataSet("owners.xml")
@CleanupUsingScript({ "cleanup.sql", "cleanup-autoincrement.sql" })
public class UserServiceIntegrationTest {
@Inject
private UserService facade;
@Deployment
public static Archive<?> createDeployment() {
return ShrinkWrap.create(WebArchive.class, "test.war")
.addClasses(UserService.class)
.addPackage(RoleCaller.class.getPackage())
.addPackage(Pet.class.getPackage())
.addAsResource("test-persistence.xml", "META-INF/persistence.xml")
.addAsWebInfResource("jboss-web.xml")
.addAsResource("arquillian.extension.persistence.properties")
.addAsResource("arquillian.extension.persistence.dbunit.properties")
.addAsWebInfResource("beans.xml", "beans.xml");
}
@Test(expected = EJBAccessException.class)
@ShouldMatchDataSet("owners.xml")
public void testGetCredentialsNoUser() {
facade.getCurrentUser();
}
}
package es.uvigo.esei.xcs.domain.entities;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
public class IsEqualToUser extends IsEqualToEntity<User> {
public IsEqualToUser(User user) {
super(user);
}
@Override
protected boolean matchesSafely(User actual) {
this.clearDescribeTo();
if (actual == null) {
this.addTemplatedDescription("actual", expected.toString());
return false;
} else {
return checkAttribute("login", User::getLogin, actual)
&& checkAttribute("role", User::getRole, actual);
}
}
@Factory
public static IsEqualToUser equalToUser(User user) {
return new IsEqualToUser(user);
}
@Factory
public static Matcher<Iterable<? extends User>> containsUsersInAnyOrder(User ... users) {
return containsEntityInAnyOrder(IsEqualToUser::equalToUser, users);
}
@Factory
public static Matcher<Iterable<? extends User>> containsUsersInAnyOrder(Iterable<User> users) {
return containsEntityInAnyOrder(IsEqualToUser::equalToUser, users);
}
}
......@@ -34,9 +34,9 @@ public class OwnersDataset {
public static Owner[] owners() {
return new Owner[] {
new Owner(EXISTENT_LOGIN, "pepepass",
new Owner(EXISTENT_LOGIN, EXISTENT_LOGIN + "pass",
new Pet(1, "Pepecat", AnimalType.CAT, new Date(946684861000L))),
new Owner(OWNER_WITH_PETS_LOGIN, "juanpass",
new Owner(OWNER_WITH_PETS_LOGIN, OWNER_WITH_PETS_LOGIN + "pass",
new Pet(2, "Max", AnimalType.CAT, new Date(946684861000L)),
new Pet(3, "Juandog", AnimalType.DOG, new Date(946684861000L))
),
......@@ -45,7 +45,7 @@ public class OwnersDataset {
new Pet(5, "Max", AnimalType.DOG, new Date(946684861000L)),
new Pet(6, "Anabird", AnimalType.BIRD, new Date(946684861000L))
),
new Owner(OWNER_WITHOUT_PETS_LOGIN, "lorenapass")
new Owner(OWNER_WITHOUT_PETS_LOGIN, OWNER_WITHOUT_PETS_LOGIN + "pass")
};
}
......@@ -117,6 +117,22 @@ public class OwnersDataset {
return new Owner(newOwnerLogin(), newOwnerPassword());
}
public static Owner newOwnerWithFreshPets() {
return new Owner(newOwnerLogin(), newOwnerPassword(),
new Pet("Jacintocat", AnimalType.CAT, new Date(946684861000L)),
new Pet("Jacintodo", AnimalType.DOG, new Date(946684861000L)),
new Pet("Jacintobird", AnimalType.BIRD, new Date(946684861000L))
);
}
public static Owner newOwnerWithPersistentPets() {
return new Owner(newOwnerLogin(), newOwnerPassword(),
new Pet(7, "Jacintocat", AnimalType.CAT, new Date(946684861000L)),
new Pet(8, "Jacintodo", AnimalType.DOG, new Date(946684861000L)),
new Pet(9, "Jacintobird", AnimalType.BIRD, new Date(946684861000L))
);
}
public static String newOwnerLogin() {
return "jacinto";
}
......@@ -125,22 +141,6 @@ public class OwnersDataset {
return "jacintopass";
}
public static Owner newOwnerWithFreshPets() {
return new Owner(newOwnerLogin(), newOwnerPassword(),
new Pet("Jacintocat", AnimalType.CAT, new Date(946684861000L)),
new Pet("Jacintodo", AnimalType.DOG, new Date(946684861000L)),
new Pet("Jacintobird", AnimalType.BIRD, new Date(946684861000L))
);
}
public static Owner newOwnerWithPersistentPets() {
return new Owner(newOwnerLogin(), newOwnerPassword(),
new Pet(7, "Jacintocat", AnimalType.CAT, new Date(946684861000L)),
new Pet(8, "Jacintodo", AnimalType.DOG, new Date(946684861000L)),
new Pet(9, "Jacintobird", AnimalType.BIRD, new Date(946684861000L))
);
}
public static String anyLogin() {
return existentLogin();
}
......@@ -148,6 +148,10 @@ public class OwnersDataset {
public static String existentLogin() {
return EXISTENT_LOGIN;
}
public static String existentPassword() {
return EXISTENT_LOGIN + "pass";
}
public static String nonExistentLogin() {
return NON_EXISTENT_LOGIN;
......@@ -156,6 +160,10 @@ public class OwnersDataset {
public static Owner anyOwner() {
return ownerWithLogin(anyLogin());
}
public static String anyOwnerPassword() {
return anyOwner().getLogin() + "pass";
}
public static Owner existentOwner() {
return ownerWithLogin(existentLogin());
......
package es.uvigo.esei.xcs.domain.entities;
public class UsersDataset {
public static final String EXISTENT_LOGIN = "jose";
public static User[] users() {
final Owner[] owners = OwnersDataset.owners();
final User[] users = new User[owners.length + 1];
users[0] = new Administrator(EXISTENT_LOGIN, EXISTENT_LOGIN + "pass");
System.arraycopy(owners, 0, users, 1, owners.length);
return users;
}
public static User existentAdmin() {
return users()[0];
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment