Skip to content

Commit

Permalink
Merge pull request #344 from sanger/addcostcode
Browse files Browse the repository at this point in the history
x1112 Addcostcode
  • Loading branch information
khelwood committed Feb 12, 2024
2 parents 9014da3 + 0d85b9d commit 661d4e4
Show file tree
Hide file tree
Showing 46 changed files with 562 additions and 154 deletions.
17 changes: 10 additions & 7 deletions src/main/java/uk/ac/sanger/sccp/stan/GraphQLMutation.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

import static java.util.Objects.requireNonNull;
import static uk.ac.sanger.sccp.utils.BasicUtils.repr;
Expand Down Expand Up @@ -439,7 +438,7 @@ public DataFetcher<Species> setSpeciesEnabled() {
}

public DataFetcher<Project> addProject() {
return adminAdd(projectService::addNew, "AddProject", "name");
return adminAdd(projectService::addNew, "AddProject", "name", User.Role.enduser);
}

public DataFetcher<Project> setProjectEnabled() {
Expand All @@ -455,7 +454,7 @@ public DataFetcher<Program> setProgramEnabled() {
}

public DataFetcher<CostCode> addCostCode() {
return adminAdd(costCodeService::addNew, "AddCostCode", "code");
return adminAdd(costCodeService::addNew, "AddCostCode", "code", User.Role.enduser);
}

public DataFetcher<CostCode> setCostCodeEnabled() {
Expand Down Expand Up @@ -876,7 +875,7 @@ public DataFetcher<OperationResult> libraryPrep() {
}

public DataFetcher<User> addUser() {
return adminAdd(userAdminService::addUser, "AddUser", "username");
return adminAdd(userAdminService::addNormalUser, "AddUser", "username");
}

public DataFetcher<User> setUserRole() {
Expand All @@ -889,12 +888,16 @@ public DataFetcher<User> setUserRole() {
};
}

private <E> DataFetcher<E> adminAdd(Function<String, E> addFunction, String functionName, String argName) {
private <E> DataFetcher<E> adminAdd(BiFunction<User, String, E> addFunction, String functionName, String argName) {
return adminAdd(addFunction, functionName, argName, User.Role.admin);
}

private <E> DataFetcher<E> adminAdd(BiFunction<User, String, E> addFunction, String functionName, String argName, User.Role role) {
return dfe -> {
User user = checkUser(dfe, User.Role.admin);
User user = checkUser(dfe, role);
String arg = dfe.getArgument(argName);
logRequest(functionName, user, repr(arg));
return addFunction.apply(arg);
return addFunction.apply(user, arg);
};
}

Expand Down
28 changes: 14 additions & 14 deletions src/main/java/uk/ac/sanger/sccp/stan/GraphQLProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private RuntimeWiring buildWiring() {
.dataFetcher("version", graphQLDataFetchers.versionInfo())
)
.type(newTypeWiring("Mutation")
.dataFetcher("registerAsEndUser", graphQLMutation.userSelfRegister(User.Role.enduser))
.dataFetcher("registerAsEndUser", graphQLMutation.userSelfRegister(User.Role.enduser)) // internal transaction
.dataFetcher("login", graphQLMutation.logIn())
.dataFetcher("logout", graphQLMutation.logOut())
.dataFetcher("register", transact(graphQLMutation.register()))
Expand All @@ -155,34 +155,34 @@ private RuntimeWiring buildWiring() {
.dataFetcher("addEquipment", transact(graphQLMutation.addEquipment()))
.dataFetcher("setEquipmentEnabled", transact(graphQLMutation.setEquipmentEnabled()))
.dataFetcher("renameEquipment", transact(graphQLMutation.renameEquipment()))
.dataFetcher("addHmdmc", transact(graphQLMutation.addHmdmc()))
.dataFetcher("addHmdmc", graphQLMutation.addHmdmc()) // internal transaction
.dataFetcher("setHmdmcEnabled", transact(graphQLMutation.setHmdmcEnabled()))
.dataFetcher("addDestructionReason", transact(graphQLMutation.addDestructionReason()))
.dataFetcher("addDestructionReason", graphQLMutation.addDestructionReason()) // internal transaction
.dataFetcher("setDestructionReasonEnabled", transact(graphQLMutation.setDestructionReasonEnabled()))
.dataFetcher("addReleaseDestination", transact(graphQLMutation.addReleaseDestination()))
.dataFetcher("addReleaseDestination", graphQLMutation.addReleaseDestination()) // internal transaction
.dataFetcher("setReleaseDestinationEnabled", transact(graphQLMutation.setReleaseDestinationEnabled()))
.dataFetcher("addReleaseRecipient", transact(graphQLMutation.addReleaseRecipient()))
.dataFetcher("updateReleaseRecipientFullName", transact(graphQLMutation.updateReleaseRecipientFullName()))
.dataFetcher("setReleaseRecipientEnabled", transact(graphQLMutation.setReleaseRecipientEnabled()))
.dataFetcher("addSpecies", transact(graphQLMutation.addSpecies()))
.dataFetcher("addSpecies", graphQLMutation.addSpecies()) // internal transaction
.dataFetcher("setSpeciesEnabled", transact(graphQLMutation.setSpeciesEnabled()))
.dataFetcher("addProject", transact(graphQLMutation.addProject()))
.dataFetcher("addProject", graphQLMutation.addProject()) // internal transaction
.dataFetcher("setProjectEnabled", transact(graphQLMutation.setProjectEnabled()))
.dataFetcher("addProgram", transact(graphQLMutation.addProgram()))
.dataFetcher("addProgram", graphQLMutation.addProgram()) // internal transaction
.dataFetcher("setProgramEnabled", transact(graphQLMutation.setProgramEnabled()))
.dataFetcher("addCostCode", transact(graphQLMutation.addCostCode()))
.dataFetcher("addCostCode", graphQLMutation.addCostCode()) // internal transaction
.dataFetcher("setCostCodeEnabled", transact(graphQLMutation.setCostCodeEnabled()))
.dataFetcher("addFixative", transact(graphQLMutation.addFixative()))
.dataFetcher("addFixative", graphQLMutation.addFixative()) // internal transaction
.dataFetcher("setFixativeEnabled", transact(graphQLMutation.setFixativeEnabled()))
.dataFetcher("addSolution", transact(graphQLMutation.addSolution()))
.dataFetcher("addSolution", graphQLMutation.addSolution()) // internal transaction
.dataFetcher("setSolutionEnabled", transact(graphQLMutation.setSolutionEnabled()))
.dataFetcher("addOmeroProject", transact(graphQLMutation.addOmeroProject()))
.dataFetcher("addOmeroProject", graphQLMutation.addOmeroProject()) // internal transaction
.dataFetcher("setOmeroProjectEnabled", transact(graphQLMutation.setOmeroProjectEnabled()))
.dataFetcher("addSlotRegion", transact(graphQLMutation.addSlotRegion()))
.dataFetcher("addSlotRegion", graphQLMutation.addSlotRegion()) // internal transaction
.dataFetcher("setSlotRegionEnabled", transact(graphQLMutation.setSlotRegionEnabled()))
.dataFetcher("addProbePanel", transact(graphQLMutation.addProbePanel()))
.dataFetcher("addProbePanel", graphQLMutation.addProbePanel()) // internal transaction
.dataFetcher("setProbePanelEnabled", transact(graphQLMutation.setProbePanelEnabled()))
.dataFetcher("addWorkType", transact(graphQLMutation.addWorkType()))
.dataFetcher("addWorkType", graphQLMutation.addWorkType()) // internal transaction
.dataFetcher("setWorkTypeEnabled", transact(graphQLMutation.setWorkTypeEnabled()))
.dataFetcher("createWork", transact(graphQLMutation.createWork()))
.dataFetcher("updateWorkStatus", transact(graphQLMutation.updateWorkStatus()))
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/uk/ac/sanger/sccp/stan/config/MailConfig.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package uk.ac.sanger.sccp.stan.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import uk.ac.sanger.sccp.utils.UCMap;

import java.util.Map;

/**
* Some config related to emails
Expand All @@ -21,6 +25,8 @@ public class MailConfig {
@Value("${stan.mail.release_cc}")
String releaseCC;

private UCMap<Boolean> adminNotifications;

/** The value to put in the "from" field of emails */
public String getSender() {
return this.sender;
Expand All @@ -40,4 +46,13 @@ public String getServiceDescription() {
public String getReleaseCC() {
return this.releaseCC;
}

public boolean isAdminNotificationEnabled(String name) {
return this.adminNotifications.getOrDefault(name, Boolean.TRUE);
}

@Autowired
public void setAdminNotifications(@Value("#{${stan.mail.admin_notify}}") Map<String, Boolean> notifications) {
this.adminNotifications = (notifications==null ? new UCMap<>(0) : new UCMap<>(notifications));
}
}
19 changes: 18 additions & 1 deletion src/main/java/uk/ac/sanger/sccp/stan/request/LoginResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@
public class LoginResult {
private String message;
private User user;
private boolean created;

public LoginResult() {}

public LoginResult(String message, User user) {
this(message, user, false);
}

public LoginResult(String message, User user, boolean created) {
this.message = message;
this.user = user;
this.created = created;
}

public String getMessage() {
Expand All @@ -35,13 +41,23 @@ public void setUser(User user) {
this.user = user;
}

/** Has a new user been created? This is used internally to trigger a notification email. */
public boolean isCreated() {
return this.created;
}

public void setCreated(boolean created) {
this.created = created;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LoginResult that = (LoginResult) o;
return (Objects.equals(this.message, that.message)
&& Objects.equals(this.user, that.user));
&& Objects.equals(this.user, that.user)
&& this.created==that.created);
}

@Override
Expand All @@ -54,6 +70,7 @@ public String toString() {
return MoreObjects.toStringHelper(this)
.add("message", message)
.add("user", user)
.add("created", created)
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package uk.ac.sanger.sccp.stan.service;

/**
* Service for sending email notifications to admin users
* @author dr6
*/
public interface AdminNotifyService {
/**
* Is the indicated admin notification enabled?
* @param name the name of the notification
* @return true if it is enabled; false if it is disabled
*/
boolean isNotificationEnabled(String name);

/**
* Replaces {@code %service} with the name of the service, for clearer email messages.
* @param string the string to modify
* @return the modified string
*/
String substitute(String string);

/**
* Issues the specified notification to admin users, if it is enabled.
* {@link #substitute} is used on the {@code heading} and {@code body} arguments.
* @param name the name of the notification
* @param heading the email heading
* @param body the text of the email
* @return true if the email was sent; false otherwise
*/
boolean issue(String name, String heading, String body);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package uk.ac.sanger.sccp.stan.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import uk.ac.sanger.sccp.stan.config.MailConfig;
import uk.ac.sanger.sccp.stan.model.User;
import uk.ac.sanger.sccp.stan.repo.UserRepo;

import java.util.List;

import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty;

/**
* @author dr6
*/
@Service
public class AdminNotifyServiceImp implements AdminNotifyService {
private final MailConfig mailConfig;
private final UserRepo userRepo;
private final EmailService emailService;

@Autowired
public AdminNotifyServiceImp(MailConfig mailConfig, UserRepo userRepo, EmailService emailService) {
this.mailConfig = mailConfig;
this.userRepo = userRepo;
this.emailService = emailService;
}

@Override
public boolean isNotificationEnabled(String name) {
return this.mailConfig.isAdminNotificationEnabled(name);
}

@Override
public String substitute(String string) {
if (!nullOrEmpty(string)) {
string = string.replace("%service", mailConfig.getServiceDescription());
}
return string;
}

@Override
public boolean issue(String name, String heading, String body) {
if (!isNotificationEnabled(name)) {
return false;
}
List<User> admins = userRepo.findAllByRole(User.Role.admin);
if (admins.isEmpty()) {
return false;
}
List<String> recipients = admins.stream().map(User::getUsername).toList();
return sendNotification(recipients, heading, body);
}

public boolean sendNotification(List<String> recipients, String heading, String body) {
heading = substitute(heading);
body = substitute(body);
return emailService.tryEmail(recipients, heading, body);
}
}
40 changes: 26 additions & 14 deletions src/main/java/uk/ac/sanger/sccp/stan/service/AuthServiceImp.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import uk.ac.sanger.sccp.stan.AuthenticationComponent;
import uk.ac.sanger.sccp.stan.Transactor;
import uk.ac.sanger.sccp.stan.config.SessionConfig;
import uk.ac.sanger.sccp.stan.model.User;
import uk.ac.sanger.sccp.stan.repo.UserRepo;
import uk.ac.sanger.sccp.stan.request.LoginResult;

import java.util.*;
import java.util.ArrayList;
import java.util.Optional;

import static uk.ac.sanger.sccp.utils.BasicUtils.repr;

Expand All @@ -27,19 +29,23 @@ public class AuthServiceImp implements AuthService {
private final UserRepo userRepo;
private final AuthenticationComponent authComp;
private final LDAPService ldapService;
private final EmailService emailService;
private final AdminNotifyService adminNotifyService;
private final UserAdminService userAdminService;
private final Transactor transactor;

@Autowired
public AuthServiceImp(SessionConfig sessionConfig,
UserRepo userRepo, AuthenticationComponent authComp,
LDAPService ldapService, EmailService emailService, UserAdminService userAdminService) {
LDAPService ldapService, AdminNotifyService adminNotifyService,
UserAdminService userAdminService,
Transactor transactor) {
this.sessionConfig = sessionConfig;
this.userRepo = userRepo;
this.authComp = authComp;
this.ldapService = ldapService;
this.emailService = emailService;
this.adminNotifyService = adminNotifyService;
this.userAdminService = userAdminService;
this.transactor = transactor;
}

/**
Expand Down Expand Up @@ -89,8 +95,7 @@ public String logOut() {
return "OK";
}

@Override
public LoginResult selfRegister(String username, String password, User.Role role) {
public LoginResult selfRegisterInTransaction(String username, String password, User.Role role) {
if (log.isInfoEnabled()) {
log.info("selfRegister attempt by {}", repr(username));
}
Expand All @@ -100,34 +105,41 @@ public LoginResult selfRegister(String username, String password, User.Role role
}
Optional<User> optUser = userRepo.findByUsername(username);
User user;
boolean created;
if (optUser.isEmpty()) {
user = userAdminService.addUser(username, role);
log.info("Login succeeded as new user {}", user);
sendNewUserEmail(user);
created = true;
} else {
user = optUser.get();
if (user.getRole()== User.Role.disabled) {
return new LoginResult("Username is disabled.", null);
}
log.info("Login succeeded for existing user {}", user);
created = false;
}

Authentication authentication = new UsernamePasswordAuthenticationToken(user, password, new ArrayList<>());
authComp.setAuthentication(authentication, sessionConfig.getMaxInactiveMinutes());
return new LoginResult("OK", user);
return new LoginResult("OK", user, created);
}

@Override
public LoginResult selfRegister(String username, String password, User.Role role) {
LoginResult result = transactor.transact("selfRegister",
() -> selfRegisterInTransaction(username, password, role));
if (result.isCreated()) {
sendNewUserEmail(result.getUser());
}
return result;
}

/**
* Tries to send an email to admin users about the new user being created.
* @param user the new user
*/
public void sendNewUserEmail(User user) {
List<User> admins = userRepo.findAllByRole(User.Role.admin);
if (admins.isEmpty()) {
return;
}
List<String> usernames = admins.stream().map(User::getUsername).toList();
String body = "User "+user.getUsername()+" has registered themself as "+user.getRole()+" on %service.";
emailService.tryEmail(usernames, "New user created on %service", body);
adminNotifyService.issue("user", "New user created on %service", body);
}
}
Loading

0 comments on commit 661d4e4

Please sign in to comment.