Browse Source

Merge branch 'pr-loginServer'

# Conflicts:
#	Jenkinsfile
pr-readme
Steffen Nitschke 4 years ago
parent
commit
06811f5ced
  1. 27
      Jenkinsfile
  2. 16
      Multi-Chess.xml
  3. 12
      build.gradle
  4. 9
      fh.fd.ci.server/build.gradle
  5. 45
      fh.fd.ci.server/src/main/java/de/fd/fh/ServerApp.java
  6. 46
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/Access.java
  7. 28
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/AccessContextEventListener.java
  8. 58
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/AccessRepository.java
  9. 152
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/AccessService.java
  10. 62
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/AccessToken.java
  11. 6
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/Role.java
  12. 13
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/events/AccountCreatedEvent.java
  13. 12
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/events/AccountDeletedEvent.java
  14. 104
      fh.fd.ci.server/src/main/java/de/fd/fh/server/access/web/AccessController.java
  15. 26
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/User.java
  16. 43
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/UserContextEventListener.java
  17. 25
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/UserId.java
  18. 52
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/UserRepository.java
  19. 48
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/UserService.java
  20. 18
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/events/ChangePasswordEvent.java
  21. 13
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/web/ChangeUserRequest.java
  22. 89
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/web/UserController.java
  23. 13
      fh.fd.ci.server/src/main/java/de/fd/fh/server/user/web/UserRequest.java
  24. 36
      fh.fd.ci.server/src/test/java/de/fd/fh/server/access/AccessContextEventListenerTest.java
  25. 42
      fh.fd.ci.server/src/test/java/de/fd/fh/server/access/AccessRepositoryTest.java
  26. 245
      fh.fd.ci.server/src/test/java/de/fd/fh/server/access/AccessServiceTest.java
  27. 31
      fh.fd.ci.server/src/test/java/de/fd/fh/server/access/AccessTokenTest.java
  28. 51
      fh.fd.ci.server/src/test/java/de/fd/fh/server/user/UserContextEventListenerTest.java
  29. 40
      fh.fd.ci.server/src/test/java/de/fd/fh/server/user/UserRepositoryTest.java
  30. 84
      fh.fd.ci.server/src/test/java/de/fd/fh/server/user/UserServiceTest.java
  31. 6
      fh.fd.ci.shared/src/main/java/de/fd/fh/shared/Utils.java
  32. 11
      fh.fd.ci.shared/src/main/java/de/fd/fh/shared/network/messages/LoginRequest.java
  33. 12
      fh.fd.ci.shared/src/main/java/de/fd/fh/shared/network/messages/RegistrateRequest.java

27
Jenkinsfile

@ -60,19 +60,20 @@ pipeline {
success { success {
script {
if ({env.BRANCH_NAME}.startsWith('pr-')) {
// git url: "git@gogs.informatik.hs-fulda.de:SteffenN/Multi-Chess.git",
// credentialsId: 'jenkins_ssh_key',
// branch: (env.BRANCH_NAME)
sh "git merge '${env.BRANCH_NAME}'"
sh "git commit -am 'Merged ${env.BRANCH_NAME} branch to master'"
sh "git push origin master"
}
}
// script {
//
// sh "'${env.BRANCH_NAME}'"
// if (${env.BRANCH_NAME}.startsWith('pr-')) {
//
//// git url: "git@gogs.informatik.hs-fulda.de:SteffenN/Multi-Chess.git",
//// credentialsId: 'jenkins_ssh_key',
//// branch: (env.BRANCH_NAME)
//
// sh "git merge '${env.BRANCH_NAME}'"
// sh "git commit -am 'Merged ${env.BRANCH_NAME} branch to master'"
// sh "git push origin master"
// }
// }
office365ConnectorSend color: 'good', office365ConnectorSend color: 'good',
message: "Build ${currentBuild.fullDisplayName} completed *successfully* (<${BUILD_URL}>).\n\n\n${CUSTOM_SCM_INFO}", message: "Build ${currentBuild.fullDisplayName} completed *successfully* (<${BUILD_URL}>).\n\n\n${CUSTOM_SCM_INFO}",
webhookUrl: "https://outlook.office.com/webhook/97618564-835e-438e-a2a7-a77b21331e1e@22877e52-e9fd-410d-91a3-817d8ab89d63/JenkinsCI/fa736de2175649a891c2957f00532027/87d23462-1d0c-4378-b4e0-05c7d5546a25" webhookUrl: "https://outlook.office.com/webhook/97618564-835e-438e-a2a7-a77b21331e1e@22877e52-e9fd-410d-91a3-817d8ab89d63/JenkinsCI/fa736de2175649a891c2957f00532027/87d23462-1d0c-4378-b4e0-05c7d5546a25"

16
Multi-Chess.xml

@ -0,0 +1,16 @@
<code_scheme name="Multi-Chess" version="173">
<option name="FORMATTER_TAGS_ENABLED" value="true" />
<codeStyleSettings language="JAVA">
<option name="BRACE_STYLE" value="2" />
<option name="CLASS_BRACE_STYLE" value="2" />
<option name="METHOD_BRACE_STYLE" value="2" />
<option name="LAMBDA_BRACE_STYLE" value="2" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
</codeStyleSettings>
</code_scheme>

12
build.gradle

@ -10,10 +10,6 @@ repositories {
jcenter() jcenter()
} }
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
subprojects { subprojects {
apply plugin: 'java' apply plugin: 'java'
@ -27,7 +23,15 @@ subprojects {
compileOnly 'org.projectlombok:lombok:1.18.16' compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16' annotationProcessor 'org.projectlombok:lombok:1.18.16'
testImplementation('org.junit.jupiter:junit-jupiter:5.7.0')
testImplementation('org.mockito:mockito-core:3.7.0')
testImplementation('org.hamcrest:hamcrest-core:2.2')
testCompileOnly 'org.projectlombok:lombok:1.18.16' testCompileOnly 'org.projectlombok:lombok:1.18.16'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.16' testAnnotationProcessor 'org.projectlombok:lombok:1.18.16'
} }
test {
useJUnitPlatform()
}
} }

9
fh.fd.ci.server/build.gradle

@ -2,4 +2,13 @@ dependencies {
implementation project(':fh.fd.ci.shared') implementation project(':fh.fd.ci.shared')
compile 'com.sparkjava:spark-core:2.9.3' compile 'com.sparkjava:spark-core:2.9.3'
compile 'dev.morphia.morphia:core:1.5.3'
compile 'com.fasterxml.jackson.core:jackson-databind:2.9.5'
task startServer(type: JavaExec){
main = "de.fd.fh.ServerApp"
description = "Start server"
classpath = sourceSets.main.runtimeClasspath
}
} }

45
fh.fd.ci.server/src/main/java/de/fd/fh/ServerApp.java

@ -1,12 +1,57 @@
package de.fd.fh; package de.fd.fh;
import de.fd.fh.server.access.AccessContextEventListener;
import de.fd.fh.server.access.AccessRepository;
import de.fd.fh.server.access.AccessService;
import de.fd.fh.server.access.web.AccessController;
import de.fd.fh.server.user.UserContextEventListener;
import de.fd.fh.server.user.web.UserController;
import de.fd.fh.server.user.UserRepository;
import de.fd.fh.server.user.UserService;
import java.util.HashSet;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import static spark.Spark.*; import static spark.Spark.*;
public class ServerApp public class ServerApp
{ {
private static AccessRepository accessRepository;
private static UserRepository userRepository;
private static final Set<Observer> listeners = new HashSet<>();
public static void main(String[] args) public static void main(String[] args)
{ {
initRepositories();
initListeners();
new AccessController((AccessService) addListeners(new AccessService(accessRepository)));
new UserController((UserService) addListeners(new UserService(userRepository)));
get("/hello", (req, res) -> "Hello World"); get("/hello", (req, res) -> "Hello World");
}
private static Object addListeners(Observable service)
{
listeners.forEach(service::addObserver);
return service;
}
private static void initListeners()
{
listeners.add(new AccessContextEventListener(accessRepository));
listeners.add(new UserContextEventListener(userRepository));
}
private static void initRepositories() {
accessRepository = new AccessRepository();
userRepository = new UserRepository();
} }
} }

46
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/Access.java

@ -0,0 +1,46 @@
package de.fd.fh.server.access;
import de.fd.fh.server.user.UserId;
import dev.morphia.annotations.Embedded;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity("login")
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Access
{
@Id
private String _id;
private String name;
private String password;
@Embedded
private UserId userId;
@Embedded
private AccessToken token;
private Role role;
void removeToken()
{
this.token = null;
}
void setToken(final AccessToken token)
{
this.token = token;
}
void updatePassword(final String newPassword)
{
this.password = newPassword;
}
}

28
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/AccessContextEventListener.java

@ -0,0 +1,28 @@
package de.fd.fh.server.access;
import de.fd.fh.server.user.events.ChangePasswordEvent;
import lombok.RequiredArgsConstructor;
import java.util.Observable;
import java.util.Observer;
@RequiredArgsConstructor
public class AccessContextEventListener implements Observer
{
private final AccessRepository accessRepository;
@Override
public void update(Observable observable, Object o)
{
if(o instanceof ChangePasswordEvent)
{
final ChangePasswordEvent event = (ChangePasswordEvent) o;
final Access access = accessRepository.findByUserId(event.getUserId());
access.updatePassword(event.getNewPassword());
accessRepository.save(access);
}
}
}

58
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/AccessRepository.java

@ -0,0 +1,58 @@
package de.fd.fh.server.access;
import com.mongodb.MongoClient;
import com.mongodb.WriteResult;
import de.fd.fh.server.user.UserId;
import dev.morphia.Datastore;
import dev.morphia.Key;
import dev.morphia.Morphia;
public class AccessRepository
{
private final Datastore datastore;
public AccessRepository()
{
System.out.println("AccessRepo");
final Morphia morphia = new Morphia();
morphia.mapPackage("de.fd.fh.server.access");
this.datastore = morphia.createDatastore(new MongoClient(), "smartwarfare");
datastore.ensureIndexes();
}
AccessRepository(Datastore datastore)
{
this.datastore = datastore;
}
public Key<Access> save(final Access access)
{
return datastore.save(access);
}
Access findByUserName(final String name)
{
return datastore.createQuery(Access.class)
.field("name").equal(name).first();
}
Access findByToken(final String token)
{
return datastore.createQuery(Access.class)
.field("token.token").equal(token).first();
}
Access findByUserId(final UserId userId)
{
return datastore.createQuery(Access.class)
.field("userId.identifier").equal(userId.getIdentifier()).first();
}
WriteResult deleteLoginByUserId(final UserId userId)
{
return datastore.delete(datastore.createQuery(Access.class)
.field("userId.identifier").equal(userId.getIdentifier()).first());
}
}

152
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/AccessService.java

@ -0,0 +1,152 @@
package de.fd.fh.server.access;
import de.fd.fh.server.access.events.AccountCreatedEvent;
import de.fd.fh.server.access.events.AccountDeletedEvent;
import de.fd.fh.server.user.UserId;
import de.fd.fh.shared.network.messages.LoginRequest;
import de.fd.fh.shared.network.messages.RegistrateRequest;
import lombok.RequiredArgsConstructor;
import org.bson.types.ObjectId;
import java.util.Base64;
import java.util.Observable;
import static spark.Spark.halt;
@RequiredArgsConstructor
public class AccessService extends Observable
{
private final AccessRepository accessRepository;
public AccessToken before(final String path, final String token) {
System.out.println("Pfad: " + path);
if (!(path.equals("/accounts/login")
|| path.equals("/accounts/registrate")
))
{
final AccessToken accessToken = authenticate(token);
if (accessToken == null)
{
halt(401);
}
return accessToken;
}
return null;
}
private AccessToken authenticate(final String bearerToken)
{
return accessRepository.findByToken(bearerToken.substring("Bearer ".length())).getToken();
}
public boolean createPlayer(RegistrateRequest message)
{
System.out.println("createPlayer: " + message);
if (userNameDoesNotExist(message.getUserName()))
{
System.out.println("Name does exist.");
return false;
}
final Access access = new Access(
new ObjectId().toHexString(),
message.getUserName(),
message.getPassword(),
UserId.random(),
null,
Role.USER
);
accessRepository.save(access);
setChanged();
notifyObservers(new AccountCreatedEvent(access.getName(),
access.getUserId()));
System.out.println("DBLogin: " + access);
return true;
}
private boolean userNameDoesNotExist(final String name)
{
final Access user = accessRepository.findByUserName(name);
return user != null;
}
public boolean logout(final String header)
{
try
{
System.out.println("logout " + header);
final Access access = accessRepository.findByToken(header.substring("Bearer ".length()));
access.removeToken();
accessRepository.save(access);
return true;
} catch (Exception e)
{
e.printStackTrace();
return false;
}
}
public LoginRequest authorization(final String header)
{
System.out.println("authorization");
final String auth = header.substring("Basic ".length());
try
{
byte[] message = Base64.getDecoder().decode(auth);
String messageStr = new String(message);
String[] user_password = messageStr.split(":");
final Access access = accessRepository.findByUserName(user_password[0]);
System.out.println(access.getName());
if (user_password[1].equals(access.getPassword()))
{
access.setToken(AccessToken.of(access));
accessRepository.save(access);
final LoginRequest loginRequest = new LoginRequest();
loginRequest.setUserId(access.getUserId().getIdentifier());
loginRequest.setToken(access.getToken().getToken());
loginRequest.setName(access.getName());
return loginRequest;
}
return null;
} catch (Exception e)
{
e.printStackTrace();
return null;
}
}
public boolean deleteAccount(final UserId userId, final AccessToken token)
{
if (!token.getUserId().getIdentifier()
.equals(userId.getIdentifier()))
{
return false;
}
if (accessRepository.deleteLoginByUserId(userId).wasAcknowledged())
{
setChanged();
notifyObservers(new AccountDeletedEvent(userId));
return true;
}
return false;
}
}

62
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/AccessToken.java

@ -0,0 +1,62 @@
package de.fd.fh.server.access;
import de.fd.fh.server.user.UserId;
import dev.morphia.annotations.Embedded;
import dev.morphia.annotations.PrePersist;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Random;
@Embedded
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class AccessToken
{
private String token;
private LocalDateTime createdDate;
private Role role;
@Embedded
private UserId userId;
static AccessToken of(final Access access)
{
return new AccessToken(
generateToken(),
LocalDateTime.now(),
access.getRole(),
access.getUserId()
);
}
@PrePersist
void prePersist()
{
this.createdDate = LocalDateTime.now();
}
private static String generateToken()
{
final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
final String lower = upper.toLowerCase();
final String numbers = "0123456789";
final String alphabet = upper + lower + numbers;
System.out.println("generate Security Token.");
Random random = new Random();
StringBuilder generatedString = new StringBuilder();
for (int i = 0; i < 64; i++) {
generatedString.append(alphabet.charAt(random.nextInt(alphabet.length())));
}
System.out.println("Token: " + generatedString);
return generatedString.toString();
}
}

6
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/Role.java

@ -0,0 +1,6 @@
package de.fd.fh.server.access;
public enum Role
{
ADMIN, USER
}

13
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/events/AccountCreatedEvent.java

@ -0,0 +1,13 @@
package de.fd.fh.server.access.events;
import de.fd.fh.server.user.UserId;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class AccountCreatedEvent
{
private final String name;
private final UserId userId;
}

12
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/events/AccountDeletedEvent.java

@ -0,0 +1,12 @@
package de.fd.fh.server.access.events;
import de.fd.fh.server.user.UserId;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class AccountDeletedEvent
{
private final UserId userId;
}

104
fh.fd.ci.server/src/main/java/de/fd/fh/server/access/web/AccessController.java

@ -0,0 +1,104 @@
package de.fd.fh.server.access.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.fd.fh.server.access.AccessService;
import de.fd.fh.server.access.AccessToken;
import de.fd.fh.server.user.UserId;
import de.fd.fh.shared.Utils;
import de.fd.fh.shared.network.messages.LoginRequest;
import de.fd.fh.shared.network.messages.RegistrateRequest;
import static spark.Spark.*;
public class AccessController
{
private final ObjectMapper objectMapper = new ObjectMapper();
public AccessController(final AccessService service)
{
before("/*",
(req, res) ->
{
final String path = req.pathInfo();
final String token = req.headers(Utils.AUTHENTICATION_HEADER);
final AccessToken accessToken = service.before(path, token);
req.session().attribute("userId",
accessToken);
});
post("/accounts/registrate",
(request, response) ->
{
final RegistrateRequest message =
objectMapper.readValue(request.body(), RegistrateRequest.class);
if (service.createPlayer(message))
{
response.status(201);
}
else
{
response.status(400);
}
return response;
}
);
post("/accounts/login",
(request, response) ->
{
final String header = request.headers(Utils.AUTHENTICATION_HEADER);
final LoginRequest login = service.authorization(header);
if (login == null)
{
response.status(401);
}
else
{
response.status(200);
response.type("application/json");
response.body(objectMapper.writeValueAsString(login));
}
return response;
});
post("/accounts/logout",
(request, response) ->
{
final String token = request.headers(Utils.AUTHENTICATION_HEADER);
if (service.logout(token))
{
response.status(200);
}
else
{
response.status(400);
}
return response;
});
delete("/accounts/:player_id",
(request, response) ->
{
final UserId userId = UserId.of(request.params(":player_id"));
final AccessToken token = request.session().attribute("userId");
if (service.deleteAccount(userId, token))
{
response.status(200);
}
else
{
response.status(400);
}
return response;
});
}
}

26
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/User.java

@ -0,0 +1,26 @@
package de.fd.fh.server.user;
import lombok.*;
@AllArgsConstructor
@Getter
public class User {
private final UserId id;
private String name;
public static User of(String name)
{
return new User(null, name);
}
public void rename(String name)
{
if (name == null)
{
return;
}
this.name = name;
}
}

43
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/UserContextEventListener.java

@ -0,0 +1,43 @@
package de.fd.fh.server.user;
import de.fd.fh.server.access.events.AccountCreatedEvent;
import de.fd.fh.server.access.events.AccountDeletedEvent;
import lombok.RequiredArgsConstructor;
import java.util.Observable;
import java.util.Observer;
@RequiredArgsConstructor
public class UserContextEventListener implements Observer
{
private final UserRepository userRepository;
@Override
public void update(
final Observable observable,
final Object event)
{
System.out.println("UserContextEventListener " + event);
if (event instanceof AccountCreatedEvent) {
handleAccountCreatedEvent((AccountCreatedEvent) event);
}
if (event instanceof AccountDeletedEvent) {
handleAccountDeletedEvent((AccountDeletedEvent) event);
}
}
private void handleAccountDeletedEvent(final AccountDeletedEvent event)
{
userRepository.deleteUserById(event.getUserId());
}
private void handleAccountCreatedEvent(final AccountCreatedEvent event)
{
System.out.println("handleAccountCreatedEvent " + event);
final User user = new User(event.getUserId(), event.getName());
System.out.println("User: " + user);
userRepository.save(user);
System.out.println("UserId: " + user.getId());
}
}

25
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/UserId.java

@ -0,0 +1,25 @@
package de.fd.fh.server.user;
import dev.morphia.annotations.Embedded;
import lombok.*;
import org.bson.types.ObjectId;
@Getter
@Embedded
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode(of = {"identifier"})
public class UserId
{
private String identifier;
public static UserId of(final String identifier)
{
return new UserId(identifier);
}
public static UserId random()
{
return new UserId(new ObjectId().toHexString());
}
}

52
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/UserRepository.java

@ -0,0 +1,52 @@
package de.fd.fh.server.user;
import com.mongodb.MongoClient;
import com.mongodb.WriteResult;
import dev.morphia.Datastore;
import dev.morphia.Key;
import dev.morphia.Morphia;
public class UserRepository {
private final Datastore datastore;
public UserRepository()
{
System.out.println("UserRepo");
final Morphia morphia = new Morphia();
morphia.mapPackage("de.fd.fh.server.user");
this.datastore = morphia.createDatastore(new MongoClient(), "smartwarfare");
datastore.ensureIndexes();
}
UserRepository(Datastore datastore)
{
this.datastore = datastore;
}
public Key<User> save(final User user)
{
return datastore.save(user);
}
public User findUserById(final UserId userId)
{
return datastore.createQuery(User.class)
.field("_id.identifier").equal(userId.getIdentifier())
.first();
}
public User findUserByName(final String name)
{
return datastore.createQuery(User.class)
.field("name").equal(name)
.first();
}
WriteResult deleteUserById(final UserId userId)
{
return datastore.delete(findUserById(userId));
}
}

48
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/UserService.java

@ -0,0 +1,48 @@
package de.fd.fh.server.user;
import de.fd.fh.server.user.events.ChangePasswordEvent;
import de.fd.fh.server.user.web.ChangeUserRequest;
import de.fd.fh.server.user.web.UserRequest;
import lombok.RequiredArgsConstructor;
import java.util.Observable;
@RequiredArgsConstructor
public class UserService extends Observable
{
private final UserRepository userRepository;
public User changePlayer(final UserId userId, final ChangeUserRequest message)
{
System.out.println("changePlayer: " + message);
User user = userRepository.findUserById(userId);
if (message.getPassword() != null)
{
setChanged();
notifyObservers(new ChangePasswordEvent(userId, message.getPassword()));
}
userRepository.save(user);
return userRepository.findUserById(userId);
}
public User getPlayer(final UserId id)
{
return userRepository.findUserById(id);
}
public UserRequest getSmallPlayer(final UserId userId)
{
final User user = userRepository.findUserById(userId);
if(user == null)
{
return null;
}
return new UserRequest(user.getId().getIdentifier(), user.getName());
}
}

18
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/events/ChangePasswordEvent.java

@ -0,0 +1,18 @@
package de.fd.fh.server.user.events;
import de.fd.fh.server.user.UserId;
import lombok.Getter;
@Getter
public class ChangePasswordEvent
{
private final String newPassword;
private final UserId userId;
public ChangePasswordEvent(final UserId userId, final String password)
{
this.newPassword = password;
this.userId = userId;
}
}

13
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/web/ChangeUserRequest.java

@ -0,0 +1,13 @@
package de.fd.fh.server.user.web;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class ChangeUserRequest
{
private final String name;
private final String password;
}

89
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/web/UserController.java

@ -0,0 +1,89 @@
package de.fd.fh.server.user.web;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.fd.fh.server.access.AccessToken;
import de.fd.fh.server.user.User;
import de.fd.fh.server.user.UserId;
import de.fd.fh.server.user.UserService;
import static spark.Spark.get;
import static spark.Spark.post;
public class UserController
{
private ObjectMapper objectMapper = new ObjectMapper();
public UserController(final UserService service)
{
post("/users",
((request, response) ->
{
final UserId userId =
((AccessToken) request.session()
.attribute("userId"))
.getUserId();
final ChangeUserRequest message = objectMapper.readValue(request.body(),
ChangeUserRequest.class);
final User user = service.changePlayer(
userId,
message);
if (user == null)
{
response.status(400);
}
else
{
response.status(200);
response.type("application/json");
return objectMapper.writeValueAsString(user);
}
return response;
}
));
get("/users",
(request, response) ->
{
final UserId userId =
((AccessToken) request.session()
.attribute("userId"))
.getUserId();
final User user = service.getPlayer(userId);
if (user == null)
{
response.status(400);
}
else
{
response.status(200);
response.type("application/json");
return objectMapper.writeValueAsString(user);
}
return response;
}
);
get("/users/:user_id",
(request, response) ->
{
final UserId userId = UserId.of(request.params(":user_id"));
final UserRequest user = service.getSmallPlayer(userId);
if (user == null)
{
response.status(404);
}
response.body(objectMapper.writeValueAsString(user));
return response;
});
}
}

13
fh.fd.ci.server/src/main/java/de/fd/fh/server/user/web/UserRequest.java

@ -0,0 +1,13 @@
package de.fd.fh.server.user.web;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public class UserRequest
{
private final String id;
private final String name;
}

36
fh.fd.ci.server/src/test/java/de/fd/fh/server/access/AccessContextEventListenerTest.java

@ -0,0 +1,36 @@
package de.fd.fh.server.access;
import de.fd.fh.server.user.UserId;
import de.fd.fh.server.user.UserRepository;
import de.fd.fh.server.user.UserService;
import de.fd.fh.server.user.events.ChangePasswordEvent;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.*;
class AccessContextEventListenerTest
{
@Test
void given_changePasswordEvent_when_passwordChanged_should_changePassword()
{
final ChangePasswordEvent event = new ChangePasswordEvent(UserId.of("12345"), "newPwd");
final AccessRepository repository = mock(AccessRepository.class);
when(repository.findByUserId(any(UserId.class)))
.thenReturn(new Access());
final ArgumentCaptor<Access> captor = ArgumentCaptor.forClass(Access.class);
new AccessContextEventListener(repository).update(null, event);
verify(repository).save(captor.capture());
assertEquals("newPwd", captor.getValue().getPassword(), "Have to be the new password");
then(repository).should().findByUserId(any(UserId.class));
then(repository).should().save(any(Access.class));
then(repository).shouldHaveNoMoreInteractions();
}
}

42
fh.fd.ci.server/src/test/java/de/fd/fh/server/access/AccessRepositoryTest.java

@ -0,0 +1,42 @@
package de.fd.fh.server.access;
import de.fd.fh.server.user.UserId;
import dev.morphia.Datastore;
import dev.morphia.Key;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class AccessRepositoryTest
{
@Mock
private Datastore datastore;
@BeforeEach
public void before()
{
datastore = mock(Datastore.class);
}
@Test
void given_newUser_when_saveUser_should_storeUserInDatabase()
{
when(datastore.save(any(Access.class)))
.thenReturn(new Key<>(Access.class, "collection", "id"));
final Access access = new Access("testId", "testName", "testPwd", UserId.of("userId"),
null, Role.USER);
final Key<Access> result = new AccessRepository(datastore).save(access);
assertThat("Key is null", result, notNullValue());
then(datastore).should().save(any(Access.class));
}
}

245
fh.fd.ci.server/src/test/java/de/fd/fh/server/access/AccessServiceTest.java

@ -0,0 +1,245 @@
package de.fd.fh.server.access;
import com.mongodb.WriteResult;
import de.fd.fh.server.user.UserId;
import de.fd.fh.shared.network.messages.LoginRequest;
import de.fd.fh.shared.network.messages.RegistrateRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import spark.HaltException;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Observable;
import java.util.Observer;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.*;
class AccessServiceTest implements Observer
{
private Object event;
@BeforeEach
void before()
{
this.event = null;
}
@Test
void given_authenticatedUser_when_serverAuthenticateUser_should_authenticateUser()
{
final Access access = new Access(
"testId",
"testName",
"testPwd",
UserId.of("12345"),
new AccessToken(
"testToken",
LocalDateTime.now(),
Role.USER,
UserId.of("12345")
),
Role.USER);
final AccessRepository repository = mock(AccessRepository.class);
when(repository.findByToken(any()))
.thenReturn(access);
final String path = "/test/path";
final String token = "testToken";
final AccessToken result = new AccessService(repository).before(path, token);
assertThat("User is not authenticated", result, notNullValue());
then(repository).should().findByToken(any());
then(repository).shouldHaveNoMoreInteractions();
}
@Test
void given_notAuthenticatedUser_when_serverAuthenticateUser_should_denyUser()
{
final Access access = new Access(
"testId",
"testName",
"testPwd",
UserId.of("12345"),
null,
Role.USER);
final AccessRepository repository = mock(AccessRepository.class);
when(repository.findByToken(any()))
.thenReturn(access);
final String path = "/test/path";
final String token = "testToken";
assertThrows(HaltException.class, () ->new AccessService(repository).before(path, token));
then(repository).should().findByToken(any());
then(repository).shouldHaveNoMoreInteractions();
}
@Test
void given_newUser_when_createUser_should_storeNewUser()
{
final RegistrateRequest request =
RegistrateRequest.of("testUser", "testPwd");
final AccessRepository repository = mock(AccessRepository.class);
when(repository.findByUserName(any()))
.thenReturn(null);
ArgumentCaptor<Access> accessCaptor = ArgumentCaptor.forClass(Access.class);
final AccessService service = new AccessService(repository);
service.addObserver(this);
final boolean result = service.createPlayer(request);
assertTrue(result);
assertThat("No event thrown", this.event, notNullValue());
verify(repository).save(accessCaptor.capture());
final Access createdAccess = accessCaptor.getValue();
assertNotNull(createdAccess, "No Access created");
assertNotNull(createdAccess.get_id(), "No Id created");
assertNotNull(createdAccess.getUserId(), "No UserId created");
assertEquals("testUser", createdAccess.getName(), "Wrong Username");
assertEquals("testPwd", createdAccess.getPassword(), "Wrong Password");
assertEquals(Role.USER.name(), createdAccess.getRole().name(), "Should be USER");
assertNull(createdAccess.getToken(), "User should not be logged in");
then(repository).should().findByUserName(any());
then(repository).should().save(any(Access.class));
then(repository).shouldHaveNoMoreInteractions();
}
@Test
void given_loggedInUser_when_logout_should_logoutUser()
{
final Access access = new Access(
"testId",
"testName",
"testPwd",
UserId.of("12345"),
null,
Role.USER
);
final AccessToken token = AccessToken.of(access);
access.setToken(token);
final AccessRepository repository = mock(AccessRepository.class);
when(repository.findByToken(any()))
.thenReturn(access);
ArgumentCaptor<Access> accessCaptor = ArgumentCaptor.forClass(Access.class);
final AccessService service = new AccessService(repository);
final boolean result = service.logout("testToken");
assertTrue(result);
verify(repository).save(accessCaptor.capture());
final Access createdAccess = accessCaptor.getValue();
assertNotNull(createdAccess, "No Access created");
assertNotNull(createdAccess.get_id(), "No Id created");
assertNotNull(createdAccess.getUserId(), "No UserId created");
assertEquals("testName", createdAccess.getName(), "Wrong Username");
assertEquals("testPwd", createdAccess.getPassword(), "Wrong Password");
assertEquals(Role.USER.name(), createdAccess.getRole().name(), "Should be USER");
assertNull(createdAccess.getToken(), "User should logged out");
then(repository).should().findByToken(any());
then(repository).should().save(any(Access.class));
then(repository).shouldHaveNoMoreInteractions();
}
@Test
void given_storedUser_when_loginUser_should_returnLoginRequest()
{
final byte[] message = Base64.getEncoder().encode("testName:testPassword".getBytes());
final String header = "Basic " + new String(message);
final Access access = new Access(
"testId",
"testName",
"testPassword",
UserId.of("12345"),
null,
Role.USER
);
final AccessRepository repository = mock(AccessRepository.class);
when(repository.findByUserName(any()))
.thenReturn(access);
final LoginRequest result = new AccessService(repository).authorization(header);
assertNotNull(result);
assertEquals(result.getName(), "testName", "Wrong UserName");
assertEquals(result.getUserId(), "12345", "Wrong Password");
assertNotNull(result.getToken(), "Not logged in");
}
@Test
void given_storedUserWithWrongPassword_when_loginUser_should_returnAccessDeny()
{
final byte[] message = Base64.getEncoder().encode("testName:testPassword".getBytes());
final String header = "Basic " + new String(message);
final AccessRepository repository = mock(AccessRepository.class);
final LoginRequest result = new AccessService(repository).authorization(header);
assertNull(result, "Return LoginRequest but wrong permissions");
}
@Test
void given_storedUser_when_deleteUser_should_deleteUser()
{
final UserId userId = UserId.of("12345");
final AccessToken token = new AccessToken(null, null, null, UserId.of("12345"));
final AccessRepository repository = mock(AccessRepository.class);
when(repository.deleteLoginByUserId(any(UserId.class)))
.thenReturn(new WriteResult(1, false, null));
final AccessService service = new AccessService(repository);
service.addObserver(this);
final boolean result = service.deleteAccount(userId, token);
assertTrue(result);
assertNotNull(event);
then(repository).should().deleteLoginByUserId(any(UserId.class));
then(repository).shouldHaveNoMoreInteractions();
}
@Test
void given_storedUser_when_deleteUserWithWrongPermission_should_doNothing()
{
final UserId userId = UserId.of("12345");
final AccessToken token = new AccessToken(null, null, null, UserId.of("98765"));
final AccessRepository repository = mock(AccessRepository.class);
when(repository.deleteLoginByUserId(any(UserId.class)))
.thenReturn(new WriteResult(1, false, null));
final AccessService service = new AccessService(repository);
service.addObserver(this);
final boolean result = service.deleteAccount(userId, token);
assertFalse(result);
assertNull(event);
then(repository).shouldHaveNoInteractions();
}
@Override
public void update(Observable o, Object arg)
{
this.event = arg;
}
}

31
fh.fd.ci.server/src/test/java/de/fd/fh/server/access/AccessTokenTest.java

@ -0,0 +1,31 @@
package de.fd.fh.server.access;
import de.fd.fh.server.user.UserId;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.*;
class AccessTokenTest
{
@Test
void given_accessData_when_createAccessToken_should_createGeneratedToken()
{
final Access access = new Access(
"testId",
"testName",
"testPwd",
UserId.of("12345"),
null,
Role.USER);
final AccessToken result = AccessToken.of(access);
assertNotNull(result.getCreatedDate());
assertNotNull(result.getToken());
assertThat(result.getToken().length(), equalTo(64));
assertEquals(result.getRole(), Role.USER);
assertEquals(result.getUserId(), UserId.of("12345"));
}
}

51
fh.fd.ci.server/src/test/java/de/fd/fh/server/user/UserContextEventListenerTest.java

@ -0,0 +1,51 @@
package de.fd.fh.server.user;
import de.fd.fh.server.access.events.AccountCreatedEvent;
import de.fd.fh.server.access.events.AccountDeletedEvent;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
class UserContextEventListenerTest
{
@Test
void given_accountCreatedEvent_when_accountWasCreated_should_createUser()
{
final AccountCreatedEvent event = new AccountCreatedEvent("testName", UserId.of("12345"));
final UserRepository repository = mock(UserRepository.class);
final ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
new UserContextEventListener(repository).update(null, event);
verify(repository).save(captor.capture());
assertNotNull(captor.getValue());
assertEquals("testName", captor.getValue().getName(), "Should have the correct name");
assertEquals("12345", captor.getValue().getId().getIdentifier(), "Should have the correct userId");
then(repository).should().save(any());
then(repository).shouldHaveNoMoreInteractions();
}
@Test
void given_accountDeletedEvent_when_accountWasDeleted_should_deleteUser()
{
final AccountDeletedEvent event = new AccountDeletedEvent(UserId.of("12345"));
final UserRepository repository = mock(UserRepository.class);
final ArgumentCaptor<UserId> captor = ArgumentCaptor.forClass(UserId.class);
new UserContextEventListener(repository).update(null, event);
verify(repository).deleteUserById(captor.capture());
assertNotNull(captor.getValue());
assertEquals("12345", captor.getValue().getIdentifier(), "No correct userId");
then(repository).should().deleteUserById(any(UserId.class));
then(repository).shouldHaveNoMoreInteractions();
}
}

40
fh.fd.ci.server/src/test/java/de/fd/fh/server/user/UserRepositoryTest.java

@ -0,0 +1,40 @@
package de.fd.fh.server.user;
import dev.morphia.Datastore;
import dev.morphia.Key;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class UserRepositoryTest
{
@Mock
private Datastore datastore;
@BeforeEach
public void before()
{
datastore = mock(Datastore.class);
}
@Test
void given_newUser_when_saveUser_should_storeUserInDatabase()
{
when(datastore.save(any(User.class)))
.thenReturn(new Key<>(User.class, "collection", "id"));
final User access = new User(UserId.of("userId"), "testName");
final Key<User> result = new UserRepository(datastore).save(access);
assertThat("Key is null", result, notNullValue());
then(datastore).should().save(any(User.class));
}
}

84
fh.fd.ci.server/src/test/java/de/fd/fh/server/user/UserServiceTest.java

@ -0,0 +1,84 @@
package de.fd.fh.server.user;
import de.fd.fh.server.user.web.ChangeUserRequest;
import de.fd.fh.server.user.web.UserRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.util.Observable;
import java.util.Observer;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
class UserServiceTest implements Observer
{
private Object event;
@BeforeEach
void before()
{
event = null;
}
@Test
void given_storedUser_when_changePassword_should_changePassword()
{
final User user =
User.of("testName");
final ChangeUserRequest request =
new ChangeUserRequest("testName", "newPassword");
final UserRepository repository = mock(UserRepository.class);
when(repository.findUserById(any(UserId.class)))
.thenReturn(user);
final UserService service = new UserService(repository);
service.addObserver(this);
final ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
service.changePlayer(UserId.of("12345"), request);
verify(repository).save(captor.capture());
assertNotNull(captor.getValue(), "Should be saved");
assertNotNull(event);
}
@Test
void given_storedUser_when_getPlayer_should_returnPlayer()
{
final User user =
User.of("testName");
final UserRepository repository = mock(UserRepository.class);
when(repository.findUserById(any(UserId.class)))
.thenReturn(user);
final User result = new UserService(repository).getPlayer(UserId.of("12345"));
assertNotNull(result);
}
@Test
void given_storedUser_when_getSmallPlayer_should_returnSmallPlayer()
{
final User user =
new User(UserId.of("12345"), "testName");
final UserRepository repository = mock(UserRepository.class);
when(repository.findUserById(any(UserId.class)))
.thenReturn(user);
final UserRequest result = new UserService(repository).getSmallPlayer(UserId.of("12345"));
assertNotNull(result);
assertEquals("12345", result.getId(), "Wrong UserId");
assertEquals("testName", result.getName(), "Wrong Name");
}
@Override
public void update(Observable o, Object arg)
{
event = arg;
}
}

6
fh.fd.ci.shared/src/main/java/de/fd/fh/shared/Utils.java

@ -0,0 +1,6 @@
package de.fd.fh.shared;
public class Utils
{
public static final String AUTHENTICATION_HEADER = "Authorization";
}

11
fh.fd.ci.shared/src/main/java/de/fd/fh/shared/network/messages/LoginRequest.java

@ -0,0 +1,11 @@
package de.fd.fh.shared.network.messages;
import lombok.Data;
@Data
public class LoginRequest
{
private String name;
private String userId;
private String token;
}

12
fh.fd.ci.shared/src/main/java/de/fd/fh/shared/network/messages/RegistrateRequest.java

@ -0,0 +1,12 @@
package de.fd.fh.shared.network.messages;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor(staticName = "of")
public class RegistrateRequest
{
private String userName;
private String password;
}
Loading…
Cancel
Save