Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x1112 Addcostcode #344

Merged
merged 3 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 25 additions & 53 deletions src/main/java/uk/ac/sanger/sccp/stan/GraphQLMutation.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import uk.ac.sanger.sccp.stan.config.SessionConfig;
import uk.ac.sanger.sccp.stan.model.*;
import uk.ac.sanger.sccp.stan.repo.UserRepo;
import uk.ac.sanger.sccp.stan.request.*;
Expand All @@ -32,9 +29,8 @@
import uk.ac.sanger.sccp.stan.service.work.WorkService;
import uk.ac.sanger.sccp.stan.service.work.WorkTypeService;

import java.util.*;
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 All @@ -45,8 +41,7 @@
@Component
public class GraphQLMutation extends BaseGraphQLResource {
Logger log = LoggerFactory.getLogger(GraphQLMutation.class);
final LDAPService ldapService;
final SessionConfig sessionConfig;
final AuthService authService;
final IRegisterService<RegisterRequest> registerService;
final IRegisterService<SectionRegisterRequest> sectionRegisterService;
final PlanService planService;
Expand Down Expand Up @@ -107,7 +102,7 @@ public class GraphQLMutation extends BaseGraphQLResource {

@Autowired
public GraphQLMutation(ObjectMapper objectMapper, AuthenticationComponent authComp,
LDAPService ldapService, SessionConfig sessionConfig,
AuthService authService,
IRegisterService<RegisterRequest> registerService,
IRegisterService<SectionRegisterRequest> sectionRegisterService,
PlanService planService, LabelPrintService labelPrintService,
Expand Down Expand Up @@ -138,8 +133,7 @@ public GraphQLMutation(ObjectMapper objectMapper, AuthenticationComponent authCo
ReactivateService reactivateService, LibraryPrepService libraryPrepService,
UserAdminService userAdminService) {
super(objectMapper, authComp, userRepo);
this.ldapService = ldapService;
this.sessionConfig = sessionConfig;
this.authService = authService;
this.registerService = registerService;
this.sectionRegisterService = sectionRegisterService;
this.planService = planService;
Expand Down Expand Up @@ -206,48 +200,22 @@ private void logRequest(String name, User user, Object request) {
}

public DataFetcher<LoginResult> logIn() {
return dataFetchingEnvironment -> {
String username = dataFetchingEnvironment.getArgument("username");
if (log.isInfoEnabled()) {
log.info("Login attempt by {}", repr(username));
}
Optional<User> optUser = userRepo.findByUsername(username);
if (optUser.isEmpty()) {
return new LoginResult("Username not in database.", null);
}
User user = optUser.get();
if (user.getRole()==User.Role.disabled) {
return new LoginResult("Username is disabled.", null);
}
String password = dataFetchingEnvironment.getArgument("password");
if (!ldapService.verifyCredentials(username, password)) {
return new LoginResult("Login failed", null);
}
Authentication authentication = new UsernamePasswordAuthenticationToken(user, password, new ArrayList<>());
authComp.setAuthentication(authentication, sessionConfig.getMaxInactiveMinutes());
log.info("Login succeeded for user {}", user);
return new LoginResult("OK", user);
return dfe -> {
String username = dfe.getArgument("username");
String password = dfe.getArgument("password");
return authService.logIn(username, password);
};
}

private String loggedInUsername() {
var auth = authComp.getAuthentication();
if (auth != null) {
var princ = auth.getPrincipal();
if (princ instanceof User) {
return ((User) princ).getUsername();
}
}
return null;
public DataFetcher<String> logOut() {
return dfe -> authService.logOut();
}

public DataFetcher<String> logOut() {
return dataFetchingEnvironment -> {
if (log.isInfoEnabled()) {
log.info("Logout requested by {}", repr(loggedInUsername()));
}
authComp.setAuthentication(null, 0);
return "OK";
public DataFetcher<LoginResult> userSelfRegister(final User.Role role) {
return dfe -> {
String username = dfe.getArgument("username");
String password = dfe.getArgument("password");
return authService.selfRegister(username, password, role);
};
}

Expand Down Expand Up @@ -470,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 @@ -486,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 @@ -907,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 @@ -920,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: 15 additions & 13 deletions src/main/java/uk/ac/sanger/sccp/stan/GraphQLProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import uk.ac.sanger.sccp.stan.model.User;

import javax.annotation.PostConstruct;
import java.io.IOException;
Expand Down Expand Up @@ -134,6 +135,7 @@ private RuntimeWiring buildWiring() {
.dataFetcher("version", graphQLDataFetchers.versionInfo())
)
.type(newTypeWiring("Mutation")
.dataFetcher("registerAsEndUser", graphQLMutation.userSelfRegister(User.Role.enduser)) // internal transaction
.dataFetcher("login", graphQLMutation.logIn())
.dataFetcher("logout", graphQLMutation.logOut())
.dataFetcher("register", transact(graphQLMutation.register()))
Expand All @@ -153,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));
}
}
3 changes: 3 additions & 0 deletions src/main/java/uk/ac/sanger/sccp/stan/repo/UserRepo.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import uk.ac.sanger.sccp.stan.model.User;

import javax.persistence.EntityNotFoundException;
import java.util.List;
import java.util.Optional;

import static uk.ac.sanger.sccp.utils.BasicUtils.repr;
Expand All @@ -14,4 +15,6 @@ public interface UserRepo extends CrudRepository<User, Integer> {
default User getByUsername(String username) throws EntityNotFoundException {
return findByUsername(username).orElseThrow(() -> new EntityNotFoundException("User not found: "+repr(username)));
}

List<User> findAllByRole(User.Role role);
}
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);

}
Loading
Loading