diff --git a/backend/src/main/java/com/synbiohub/sbh3/controllers/AdminController.java b/backend/src/main/java/com/synbiohub/sbh3/controllers/AdminController.java index a5bd3a55..5faf147f 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/controllers/AdminController.java +++ b/backend/src/main/java/com/synbiohub/sbh3/controllers/AdminController.java @@ -1,6 +1,9 @@ package com.synbiohub.sbh3.controllers; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.synbiohub.sbh3.security.model.User; import com.synbiohub.sbh3.services.AdminService; import com.synbiohub.sbh3.services.SearchService; import com.synbiohub.sbh3.services.UserService; @@ -8,9 +11,12 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.AllArgsConstructor; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; +import java.io.File; import java.io.IOException; +import java.util.HashMap; import java.util.Map; @RestController @@ -20,12 +26,12 @@ public class AdminController { private final AdminService adminService; private final UserService userService; private final SearchService searchService; - - @GetMapping(value = "/admin/sparql") @ResponseBody - public String runAdminSparqlQuery(@RequestParam String query) throws IOException { - Authentication auth = userService.checkAuthentication(); + public String runAdminSparqlQuery(@RequestParam String query, HttpServletRequest request) throws Exception { + String inputToken = request.getHeader("X-authorization"); +// Authentication auth = userService.checkAuthentication(inputToken); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth == null) { return null; } @@ -34,14 +40,19 @@ public String runAdminSparqlQuery(@RequestParam String query) throws IOException @GetMapping(value = "/admin") @ResponseBody - public String status(@RequestParam Map allParams, HttpServletRequest request) throws IOException { - return adminService.getStatus().toString(); + public String status(@RequestParam Map allParams, HttpServletRequest request) throws Exception { + return adminService.getStatus(request).toString(); } + /** + * This will just run a basic query on Virtuoso. If the result exists, return "Alive". Otherwise, return "Dead". + * @return + */ @GetMapping(value = "/admin/virtuoso") @ResponseBody - public String getVirtuosoStatus(@RequestParam Map allParams, HttpServletRequest request) { - return null; + public String getVirtuosoStatus() { + boolean vStatus = adminService.getDatabaseStatus(); + return vStatus ? "Alive" : "Dead"; } @GetMapping(value = "/admin/graphs") @@ -54,8 +65,11 @@ public String getGraph(@RequestParam Map allParams, HttpServletRe @GetMapping(value = "/admin/log") @ResponseBody public String getLog(@RequestParam Map allParams, HttpServletRequest request) { - // prints the spring.log? - return null; + try { + return adminService.getLogs(); + } catch (Exception e) { + return "Error reading spring.log file " + e.getMessage(); + } } @GetMapping(value = "/admin/mail") @@ -70,6 +84,7 @@ public String updateMailSettings(@RequestParam Map allParams, Htt return null; } + //TODO: get admin plugins needs to be public, post admin plugins need to be admin only @GetMapping(value = "/admin/plugins") @ResponseBody public String getPlugins(@RequestParam Map allParams, HttpServletRequest request) throws IOException { @@ -78,14 +93,38 @@ public String getPlugins(@RequestParam Map allParams, HttpServlet @PostMapping(value = "/admin/savePlugin") @ResponseBody - public String savePlugin(@RequestParam Map allParams, HttpServletRequest request) { - return null; + public String savePlugin(@RequestParam Map allParams, HttpServletRequest request) throws IOException { + if (!ConfigUtil.checkLocalJson("plugins")) { + ConfigUtil.set(ConfigUtil.getLocaljson(),"plugins", ConfigUtil.get("plugins")); + ConfigUtil.refreshLocalJson(); + } + ObjectMapper mapper = new ObjectMapper(); + if (allParams.get("id").equals("New")) { + int pluginArraySize = ConfigUtil.get("plugins").get(allParams.get("category")).size(); + ArrayNode pluginArray = adminService.saveNewPlugin(allParams); + if (pluginArraySize != pluginArray.size()) { + mapper.writerWithDefaultPrettyPrinter().writeValue(new File("data/config.local.json"), ConfigUtil.getLocaljson()); + return "Plugin (New, " + allParams.get("name") + ", " + allParams.get("url") + "/, " + allParams.get("category") + ") saved successfully"; + } else { + return "Error saving new plugin: " + allParams.get("name"); + } + } else { + adminService.updatePlugin(allParams); + return "Plugin " + allParams.get("name") + " updated."; + } } @PostMapping(value = "/admin/deletePlugin") @ResponseBody public String deletePlugin(@RequestParam Map allParams, HttpServletRequest request) { - return null; + try { + ObjectMapper mapper = new ObjectMapper(); + adminService.deletePlugin(allParams.get("category"), allParams.get("id")); + mapper.writerWithDefaultPrettyPrinter().writeValue(new File("data/config.local.json"), ConfigUtil.getLocaljson()); + return "Plugin ("+ allParams.get("id") + ", " + allParams.get("category") + ") deleted successfully"; + } catch (IOException e) { + throw new RuntimeException(e); + } } @GetMapping(value = "/admin/registries") @@ -109,8 +148,19 @@ public void deleteRegistry(@RequestBody String webOfRegistryName) { @PostMapping(value = "/admin/setAdministratorEmail") @ResponseBody - public String setAdminEmail(@RequestParam Map allParams, HttpServletRequest request) { - return null; + public String setAdminEmail(String newEmail) throws Exception { + User adminUser = userService.getUserProfile(); + try { + if (adminUser.getIsAdmin()) { + Map params = new HashMap<>(); + params.put("email", newEmail); + userService.updateUserProfile(params); + return "Updated administrator email"; + } + } catch (Exception e) { + return "Unable to update administrator email " + e.getMessage(); + } + return "Unable to update administrator email, but no error was thrown"; } @PostMapping(value = "/admin/retrieveFromWebOfRegistries") @@ -122,14 +172,14 @@ public String updateRegistries(@RequestParam Map allParams, HttpS @PostMapping(value = "/admin/federate") @ResponseBody public String sendFederateRequest(@RequestParam Map allParams, HttpServletRequest request) { - return null; + return "This is send Federate Request. It is not yet implemented."; } @GetMapping(value = "/admin/remotes") @ResponseBody - public String getRemotes(@RequestParam Map allParams, HttpServletRequest request) { + public String getRemotes() throws IOException { // TODO: need to check format of remotes - return null; + return ConfigUtil.get("remotes").toString(); } @PostMapping(value = "/admin/saveRemote") //benchling and ice remotes have different params @@ -162,12 +212,19 @@ public String updateExplorerConfig(@RequestParam Map allParams, H return null; } + /** + * I am not sure what this should be. It is a post request, but right now, all it's doing is getting the status + * @return + */ + // TODO: check if this method should be returning SBOL Explorer status @PostMapping(value = "/admin/explorerUpdateIndex") @ResponseBody - public String updateExplorerIndex(@RequestParam Map allParams, HttpServletRequest request) { - return null; + public String updateExplorerIndex() throws IOException { + boolean SBOLExplorerStatus = adminService.getSBOLExplorerStatus(); + return SBOLExplorerStatus ? "SBOLExplorer is not enabled" : "SBOLExplorer is enabled"; } + //TODO: get admin theme needs to be public, post admin theme needs to be admin only @GetMapping(value = "/admin/theme") @ResponseBody public String getTheme() throws IOException { diff --git a/backend/src/main/java/com/synbiohub/sbh3/controllers/UserController.java b/backend/src/main/java/com/synbiohub/sbh3/controllers/UserController.java index 3ef00fbf..52c9681c 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/controllers/UserController.java +++ b/backend/src/main/java/com/synbiohub/sbh3/controllers/UserController.java @@ -1,81 +1,72 @@ package com.synbiohub.sbh3.controllers; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.synbiohub.sbh3.dto.LoginDTO; import com.synbiohub.sbh3.dto.UserRegistrationDTO; -import com.synbiohub.sbh3.security.CustomUserService; import com.synbiohub.sbh3.security.customsecurity.AuthenticationResponse; import com.synbiohub.sbh3.security.model.User; +import com.synbiohub.sbh3.security.repo.AuthRepository; import com.synbiohub.sbh3.services.UserService; import com.synbiohub.sbh3.utils.ConfigUtil; import com.synbiohub.sbh3.utils.RestClient; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.web.bind.annotation.*; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.HashMap; import java.util.Map; @RestController @RequiredArgsConstructor @Slf4j public class UserController { - - private final CustomUserService customUserService; -// private final AuthenticationManager authenticationManager; private final UserService userService; private final RestClient restClient; private final ObjectMapper mapper; - + private final AuthRepository authRepository; @PostMapping(value = "/login", produces = "text/plain") - public ResponseEntity login(@RequestParam String email, @RequestParam String password) { + public ResponseEntity login(@RequestParam String email, @RequestParam String password) { + if (email.isEmpty() || password.isEmpty()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Please enter your e-mail address and password."); + } try { - String username = email; + String username; if (userService.isValidEmail(email)) { - username = userService.getUserByEmail(email).getUsername(); + try { + username = userService.getUserByEmail(email).getUsername(); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Your e-mail address was not recognized."); + } + } else { + try { + username = userService.getUserByUsername(email).getUsername(); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Please enter a valid email address or username."); + } } - LoginDTO loginRequest = LoginDTO - .builder() - .username(username) - .password(password) - .build(); - AuthenticationResponse response = userService.authenticate(loginRequest); - return ResponseEntity.ok(response.getToken()); + return ResponseEntity.ok(userService.loginUser(username, password)); } catch (Exception e) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Your e-mail address was not recognized."); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Your password was not recognized."); } - } - // TODO: change what logout does, maybe not invalidate session, but invalidate current auth token - @PostMapping(value = "/logout") - public ResponseEntity logout(HttpServletRequest request, HttpServletResponse response) throws Exception { + /** + * Logs the current user out of SBH. + * Endpoint cannot be /logout as this gets intercepted by security configuration and doesn't go through here + * @param request + * @return + * @throws Exception + */ + @PostMapping(value = "/do_logout") + public ResponseEntity logout(HttpServletRequest request) throws Exception { log.info("Received logout request"); - Authentication auth = userService.checkAuthentication(); - if (auth != null) { - new SecurityContextLogoutHandler().logout(request, response, auth); - return ResponseEntity.ok("User logged out successfully"); - } else { - throw new Exception("No user is currently logged in."); - } + return ResponseEntity.ok(userService.logoutUser(request)); } @PostMapping(value = "/register") - public ResponseEntity registerNewUser(@RequestParam String username, @RequestParam String name, @RequestParam String affiliation, @RequestParam String email, @RequestParam String password1, @RequestParam String password2) { + public ResponseEntity registerNewUser(@RequestParam String username, @RequestParam String name, @RequestParam String affiliation, @RequestParam String email, @RequestParam String password1, @RequestParam String password2) { try { log.info("Registering a new user."); UserRegistrationDTO userRegistrationDTO = UserRegistrationDTO @@ -90,11 +81,11 @@ public ResponseEntity registerNewUser(@RequestParam String username, @RequestPar AuthenticationResponse response = userService.register(userRegistrationDTO); return ResponseEntity.ok(response.getToken()); } catch (Exception e) { - log.error("Error creating a new account."); - e.printStackTrace(); - return new ResponseEntity(HttpStatus.BAD_REQUEST); + log.error("Error registering a new account."); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Error registering a new account."); } } + // // @PostMapping(value = "/resetPassword") // public ResponseEntity resetPassword(@RequestParam Map allParams) { @@ -105,95 +96,58 @@ public ResponseEntity registerNewUser(@RequestParam String username, @RequestPar // public ResponseEntity setNewPassword(@RequestParam Map allParams) { // return new ResponseEntity<>(HttpStatus.OK); // } -// + @GetMapping(value = "/profile", produces = "text/plain") - public ResponseEntity getProfile() throws JsonProcessingException, CloneNotSupportedException { + public ResponseEntity getProfile(HttpServletRequest request) throws Exception { + String inputToken = request.getHeader("X-authorization"); var user = userService.getUserProfile(); if (user == null) - return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Error retrieving user profile."); return ResponseEntity.ok(mapper.writeValueAsString(user)); } - // Only updates the fields name, email, and affiliation currently + /** + * Changes user's profile fields. + * Only updates the fields name, email, and affiliation currently + */ @PostMapping(value = "/profile", produces = "text/plain") - public ResponseEntity updateProfile(@RequestParam Map allParams) throws JsonProcessingException, CloneNotSupportedException { - User updatedUser = userService.updateUser(allParams); - if (updatedUser == null) { - return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + public ResponseEntity updateProfile(@RequestParam Map allParams, HttpServletRequest request) throws Exception { + User updatedUser; + try { + String inputToken = request.getHeader("X-authorization"); + updatedUser = userService.updateUserProfile(allParams); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not found."); } User copyUser = (User) updatedUser.clone(); copyUser.setPassword(""); + log.info(copyUser.toString()); return ResponseEntity.ok("Profile updated successfully"); } + /** + * First time setup of Synbiohub + * @param allParams + * @return + */ @PostMapping(value = "/setup") public ResponseEntity setup(@RequestBody Map allParams) { log.info(String.valueOf(allParams)); - String fileName = "config.local.json"; - String workingDirectory = System.getProperty("user.dir") + "/data"; - File file = new File(workingDirectory + File.separator + fileName); - - ObjectMapper mapper = new ObjectMapper(); - mapper.enable(SerializationFeature.INDENT_OUTPUT); - - Map userParams = new HashMap<>(); - UserRegistrationDTO userRegistrationDTO = UserRegistrationDTO - .builder() - .username((String) allParams.get("userName")) - .name((String) allParams.get("userFullName")) - .affiliation((String) allParams.get("affiliation")) - .email((String) allParams.get("userEmail")) - .password1((String) allParams.get("userPassword")) - .password2((String) allParams.get("userPasswordConfirm")) - .build(); - userService.register(userRegistrationDTO); - - allParams.remove("userName"); - allParams.remove("userFullName"); - allParams.remove("affiliation"); - allParams.remove("userEmail"); - allParams.remove("userPassword"); - allParams.remove("userPasswordConfirm"); - - try { - if (file.createNewFile()) { - allParams.put("sparqlEndpoint", "http://virtuoso3:8890/sparql"); - allParams.put("graphStoreEndpoint", "http://virtuoso3:8890/sparql-graph-crud-auth/"); - allParams.put("firstLaunch", false); - allParams.put("version", 1); - Map themeParams = new HashMap<>(); - themeParams.put("default", allParams.get("color")); - allParams.put("themeParameters", themeParams); - allParams.remove("color"); - // TODO: Setup should add a local version to web of registries + return ResponseEntity.ok(userService.setupInstance(allParams)); + } - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter(); - String json = writer.writeValueAsString(allParams); - FileWriter fw = new FileWriter(file.getAbsoluteFile()); - BufferedWriter bw = new BufferedWriter(fw); - bw.write(json); - bw.close(); - ConfigUtil.refreshLocalJson(); - log.info("Setup successful!"); - return ResponseEntity.ok("Setup Successful"); - } else { - log.info("Local file already exists. Setup proceeds."); - return ResponseEntity.ok("File already exists!"); - } - } catch (IOException e) { - log.error("Setup failed."); - return ResponseEntity.ok("Failed to create file!"); - } + /** + * Logs out all users from current instance of SBH + * @return + */ + @DeleteMapping(value = "/cleanAuthRepo") + public String cleanAuthRepo() { + authRepository.deleteAll(); + return "Cleaned."; } @GetMapping("/firstLaunched") public Boolean checkFirstLaunch() { - String fileName = "config.local.dup.json"; - String workingDirectory = System.getProperty("user.dir"); - File file = new File(workingDirectory + File.separator + fileName); - - return file.exists(); + return ConfigUtil.isLaunched(); } } diff --git a/backend/src/main/java/com/synbiohub/sbh3/security/CustomUserDetails.java b/backend/src/main/java/com/synbiohub/sbh3/security/CustomUserDetails.java index 943c2d72..127b315d 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/security/CustomUserDetails.java +++ b/backend/src/main/java/com/synbiohub/sbh3/security/CustomUserDetails.java @@ -1,6 +1,5 @@ package com.synbiohub.sbh3.security; -import com.synbiohub.sbh3.security.model.User; import lombok.Builder; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -8,6 +7,7 @@ import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; @Builder public class CustomUserDetails implements UserDetails { @@ -19,10 +19,14 @@ public class CustomUserDetails implements UserDetails { private Boolean accountNonLocked; private Boolean credentialsNonExpired; private Boolean enabled; + private Collection roles; @Override public Collection getAuthorities() { - return this.authorities; + // Convert roles to authorities + return roles.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); } @Override diff --git a/backend/src/main/java/com/synbiohub/sbh3/security/config/SecurityConfig.java b/backend/src/main/java/com/synbiohub/sbh3/security/config/SecurityConfig.java index ad8c08fe..00e9c2d3 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/security/config/SecurityConfig.java +++ b/backend/src/main/java/com/synbiohub/sbh3/security/config/SecurityConfig.java @@ -26,6 +26,7 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.channel.ChannelProcessingFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity @@ -47,14 +48,15 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final AuthenticationProvider authenticationProvider; + //TODO: ADD isOwnedBy method @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests() - .requestMatchers("/**").permitAll() + .requestMatchers("/setup", "/login", "/register", "/search", "/search/**", "/searchCount", "/searchCount/**", "/twins", "/uses", "/similar", "/sbol", "/sbolnr", "/metadata", "/gb", "/fasta", "/gff", "/download", "/public/**", "/sparql", "/ComponentDefinition/**", "/**/count", "/count").permitAll() + .anyRequest().authenticated() .and() - .authorizeHttpRequests((auth) -> auth.anyRequest().authenticated()) - .csrf((csrf) -> csrf.disable()) + .csrf().disable() .httpBasic(Customizer.withDefaults()) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) diff --git a/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/EndpointProperties.java b/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/EndpointProperties.java new file mode 100644 index 00000000..91e08b48 --- /dev/null +++ b/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/EndpointProperties.java @@ -0,0 +1,23 @@ +package com.synbiohub.sbh3.security.customsecurity; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; +import java.util.Map; + +@Configuration +@ConfigurationProperties +public class EndpointProperties { + private Map> roleToEndpointPermissions; + + public List getRoleToEndpointPermissions(String role) { + return roleToEndpointPermissions.get(role); + } + + public void setRoleToEndpointPermissions(Map> roleToEndpointPermissions) { + this.roleToEndpointPermissions = roleToEndpointPermissions; + } +} + + diff --git a/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/JwtAuthenticationFilter.java b/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/JwtAuthenticationFilter.java index e1d7ff9b..25711ddb 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/JwtAuthenticationFilter.java @@ -1,20 +1,27 @@ package com.synbiohub.sbh3.security.customsecurity; +import io.jsonwebtoken.Claims; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; import org.springframework.web.filter.OncePerRequestFilter; import javax.print.DocFlavor; import java.io.IOException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Component @RequiredArgsConstructor @@ -24,6 +31,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; + @Autowired + private EndpointProperties endpointProperties; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); @@ -39,6 +49,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } jwt = authHeader == null ? xauthHeader : authHeader; username = jwtService.extractUsername(jwt); + var claim = jwtService.extractAllClaims(jwt); + List userRoles = convertAuthoritiesToRoles(claim); + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtService.isTokenValid(jwt, userDetails)) { @@ -49,7 +62,38 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse SecurityContextHolder.getContext().setAuthentication(authToken); } } + + Set allowedEndpoints = new HashSet<>(endpointProperties.getRoleToEndpointPermissions("USER")); + String requestedEndpoint = request.getRequestURI(); + for (String role : userRoles) { + allowedEndpoints.addAll(endpointProperties.getRoleToEndpointPermissions(role)); + } + boolean isEndpointAllowed = false; + AntPathMatcher pathMatcher = new AntPathMatcher(); + for (String allowedEndpoint : allowedEndpoints) { + if (pathMatcher.match(allowedEndpoint, requestedEndpoint)) { + isEndpointAllowed = true; + break; + } + } + + if (!isEndpointAllowed) { + // User's role does not have the correct permission for the requested endpoint. + response.sendError(HttpServletResponse.SC_FORBIDDEN, "You don't have the permission to access this resource."); + return; + } filterChain.doFilter(request, response); } + + private List convertAuthoritiesToRoles(Claims claim) { + List> userAuthorities = (List>) claim.get("Role"); + + List userRoles = new ArrayList<>(); + for (LinkedHashMap authority : userAuthorities) { + String role = authority.values().iterator().next(); + userRoles.add(role); + } + return userRoles; + } } diff --git a/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/JwtService.java b/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/JwtService.java index e7e2a423..e004b519 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/JwtService.java +++ b/backend/src/main/java/com/synbiohub/sbh3/security/customsecurity/JwtService.java @@ -33,12 +33,13 @@ public String generateToken(UserDetails userDetails) { } public String generateToken(Map extraClaims, UserDetails userDetails) { + extraClaims.put("Role", userDetails.getAuthorities()); return Jwts .builder() .setClaims(extraClaims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 10))) // Tokens last for 10 minutes + .setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 60))) // Tokens last for 1 hour .signWith(getSigningKey(), SignatureAlgorithm.HS256) .compact(); } @@ -56,7 +57,7 @@ private Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } - private Claims extractAllClaims(String token) { + public Claims extractAllClaims(String token) { return Jwts .parserBuilder() .setSigningKey(getSigningKey()) diff --git a/backend/src/main/java/com/synbiohub/sbh3/security/model/AuthCodes.java b/backend/src/main/java/com/synbiohub/sbh3/security/model/AuthCodes.java new file mode 100644 index 00000000..c9af6d0c --- /dev/null +++ b/backend/src/main/java/com/synbiohub/sbh3/security/model/AuthCodes.java @@ -0,0 +1,23 @@ +package com.synbiohub.sbh3.security.model; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Data +@Builder +@Table(name="auth", schema = "public") +@NoArgsConstructor +@AllArgsConstructor +public class AuthCodes { + + @Id + @GeneratedValue(strategy= GenerationType.IDENTITY) + private Long id; + + @Column(name="name") + private String name; + + @Column(name = "auth") + private String auth; +} diff --git a/backend/src/main/java/com/synbiohub/sbh3/security/model/User.java b/backend/src/main/java/com/synbiohub/sbh3/security/model/User.java index 41fa3a09..c2880123 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/security/model/User.java +++ b/backend/src/main/java/com/synbiohub/sbh3/security/model/User.java @@ -38,7 +38,7 @@ public class User implements UserDetails, Cloneable{ private String email; @Column(name = "affiliation") - private String affiliation; + private String affiliation = ""; @Enumerated(EnumType.STRING) @Column(name = "user_role") diff --git a/backend/src/main/java/com/synbiohub/sbh3/security/repo/AuthRepository.java b/backend/src/main/java/com/synbiohub/sbh3/security/repo/AuthRepository.java new file mode 100644 index 00000000..5557b91c --- /dev/null +++ b/backend/src/main/java/com/synbiohub/sbh3/security/repo/AuthRepository.java @@ -0,0 +1,20 @@ +package com.synbiohub.sbh3.security.repo; + +import com.synbiohub.sbh3.security.model.AuthCodes; +import com.synbiohub.sbh3.security.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface AuthRepository extends JpaRepository { + Optional findByName(String name); + + Optional> findAuthCodesByName(String name); + + +} diff --git a/backend/src/main/java/com/synbiohub/sbh3/services/AdminService.java b/backend/src/main/java/com/synbiohub/sbh3/services/AdminService.java index 1645df71..36d09075 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/services/AdminService.java +++ b/backend/src/main/java/com/synbiohub/sbh3/services/AdminService.java @@ -2,24 +2,35 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.synbiohub.sbh3.sparql.SPARQLQuery; import com.synbiohub.sbh3.utils.ConfigUtil; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Map; @Service @RequiredArgsConstructor +@Slf4j public class AdminService { private final UserService userService; + private final SearchService searchService; private ObjectMapper mapper = new ObjectMapper(); - public JsonNode getStatus() throws IOException { - Authentication authentication = userService.checkAuthentication(); - if (authentication == null) { + public JsonNode getStatus(HttpServletRequest request) throws Exception { + String inputToken = request.getHeader("X-authorization"); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null) { return null; } ObjectNode status = mapper.createObjectNode(); @@ -46,4 +57,79 @@ public String getTheme() throws IOException { String result = mapper.writeValueAsString(json); return result; } + + public Boolean getDatabaseStatus() { + SPARQLQuery statusQuery = new SPARQLQuery("src/main/java/com/synbiohub/sbh3/sparql/GetDatabaseStatus.sparql"); + try { + var result = searchService.SPARQLQuery(statusQuery.getQuery()); + if (result.getBytes().length > 0) { + return true; + } else { + return false; + } + } catch (Exception e) { + return false; + } + } + + public String getLogs() throws IOException { + String logPath = System.getProperty("user.dir") + "/data/spring.log"; + return new String(Files.readAllBytes(Paths.get(logPath))); + } + + public Boolean getSBOLExplorerStatus() throws IOException { + return ConfigUtil.get("useSBOLExplorer").asBoolean(); + } + + public ArrayNode saveNewPlugin(Map allParams) throws IOException { + ArrayNode pluginArray = (ArrayNode) ConfigUtil.get("plugins").get(allParams.get("category")); + if (!checkPluginName(pluginArray, allParams.get("name"))) { + JsonNode pluginMap = castParamsToPlugin(allParams, pluginArray.size()); + pluginArray.add(pluginMap); + log.info("Plugin: " + allParams.get("name") + " saved"); + return pluginArray; + } + log.error("Error saving new plugin: " + allParams.get("name")); + return pluginArray; + } + + public ArrayNode deletePlugin(String category, String id) throws IOException { + JsonNode plugins = ConfigUtil.get("plugins").get(category); + + ArrayNode pluginArray = mapper.createArrayNode(); + if (plugins.isArray()) { + pluginArray = (ArrayNode) plugins; + for (int i = 0; i < pluginArray.size(); i++) { + JsonNode innerNode = pluginArray.get(i); + var temp1 = innerNode.get("index"); + if (innerNode.get("index").asInt() == (Integer.parseInt(id))) { + pluginArray.remove(i); + break; + } + } + } + return pluginArray; + } + + public String updatePlugin(Map allParams) throws IOException { + return "Plugin updated"; + } + + private ObjectNode castParamsToPlugin(Map allParams, int arraySize) { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode pluginMap = mapper.createObjectNode(); + pluginMap.put("name", allParams.get("name")); + pluginMap.put("url", allParams.get("url")+"/"); + pluginMap.put("index", arraySize); + return pluginMap; + } + + private Boolean checkPluginName(ArrayNode pluginArray, String name) { + for (String n : pluginArray.findValuesAsText("name")) { + if (n.equals(name)) { + return true; + } + } + return false; + } } diff --git a/backend/src/main/java/com/synbiohub/sbh3/services/DownloadService.java b/backend/src/main/java/com/synbiohub/sbh3/services/DownloadService.java index 0b889c06..526d79cd 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/services/DownloadService.java +++ b/backend/src/main/java/com/synbiohub/sbh3/services/DownloadService.java @@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; +import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFDataMgr; import org.apache.jena.riot.RDFFormat; import org.sbolstandard.core2.SBOLDocument; @@ -16,6 +17,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.util.Arrays; import java.util.Collections; @@ -157,7 +159,13 @@ public Model getRecursiveModel(String uri) throws IOException { subjectResults = searchService.SPARQLRDFXMLQuery(subjectQuery); } Model tempModel = ModelFactory.createDefaultModel(); - tempModel.read(new ByteArrayInputStream(subjectResults), null); + if (subjectResults.length == 0) { + InputStream inputStream = new ByteArrayInputStream(subjectResults); + RDFDataMgr.read(model, inputStream, Lang.TURTLE); + } else { + tempModel.read(new ByteArrayInputStream(subjectResults), null); + } + if (model.size() > 10000) { int counter = 1; var offset = model.size(); diff --git a/backend/src/main/java/com/synbiohub/sbh3/services/SearchService.java b/backend/src/main/java/com/synbiohub/sbh3/services/SearchService.java index b04ab3c1..c9b29241 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/services/SearchService.java +++ b/backend/src/main/java/com/synbiohub/sbh3/services/SearchService.java @@ -7,13 +7,12 @@ import com.synbiohub.sbh3.controllers.SearchController; import com.synbiohub.sbh3.sparql.SPARQLQuery; import com.synbiohub.sbh3.utils.ConfigUtil; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; +import org.springframework.http.*; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import java.io.IOException; @@ -353,6 +352,8 @@ public String SPARQLQuery(String query) throws IOException { params.put("query", query); url = ConfigUtil.get("sparqlEndpoint").asText() + "?default-graph-uri={default-graph-uri}&query={query}&format=json&"; +// url = ConfigUtil.get("sparqlEndpoint").asText() + "?default-graph-uri={default-graph-uri}&query={query}"; + // has to be the first one. without format json, getting root collections fails return restTemplate.getForObject(url, String.class, params); } @@ -383,15 +384,29 @@ public byte[] queryOldSBHSparqlEndpoint(String WOREndpoint, String query) throws RestTemplate restTemplate = new RestTemplate(); String url; HashMap params = new HashMap<>(); +// params.put("default-graph-uri", ConfigUtil.get("defaultGraph").asText()); params.put("query", query); + params.put("format", "application/rdf+xml"); HttpHeaders httpHeaders = new HttpHeaders(); +// httpHeaders.add("Accept", "application/json"); httpHeaders.add("Accept", "application/rdf+xml"); - HttpEntity entity = new HttpEntity(httpHeaders); - - url = ConfigUtil.get("webOfRegistries").get(WOREndpoint).asText() + "/sparql?query={query}"; - - var rest = restTemplate.exchange(url, HttpMethod.GET, entity, String.class, params); + HttpEntity entity = new HttpEntity<>("body", httpHeaders); +// url = WOREndpoint + "/sparql?query="+query; +// var result = restTemplate.getForObject(url, String.class); +// url = WOREndpoint + "/sparql?default-graph-uri={default-graph-uri}&query={query}"; + url = WOREndpoint + "/sparql?query={query}"; + ResponseEntity rest; + try { + rest = restTemplate.getForEntity(url, String.class, entity); + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == HttpStatus.NOT_ACCEPTABLE) { + byte[] emptyByteArray = new byte[0]; + return emptyByteArray; + } else { + throw e; + } + } return rest.getBody().getBytes(StandardCharsets.UTF_8); } diff --git a/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java b/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java index f0931ffb..63e234c9 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java +++ b/backend/src/main/java/com/synbiohub/sbh3/services/UserService.java @@ -3,27 +3,35 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializationFeature; import com.synbiohub.sbh3.dto.LoginDTO; import com.synbiohub.sbh3.dto.UserRegistrationDTO; import com.synbiohub.sbh3.security.customsecurity.AuthenticationResponse; import com.synbiohub.sbh3.security.customsecurity.JwtService; +import com.synbiohub.sbh3.security.model.AuthCodes; import com.synbiohub.sbh3.security.model.Role; import com.synbiohub.sbh3.security.model.User; +import com.synbiohub.sbh3.security.repo.AuthRepository; import com.synbiohub.sbh3.security.repo.UserRepository; -import com.synbiohub.sbh3.security.CustomUserService; import com.synbiohub.sbh3.sparql.SPARQLQuery; import com.synbiohub.sbh3.utils.ConfigUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.web.context.request.RequestContextHolder; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.sql.*; import java.util.*; @@ -31,18 +39,97 @@ @Service @RequiredArgsConstructor +@Slf4j public class UserService { private final UserRepository userRepository; - private final SearchService searchService; - - private final CustomUserService customUserService; - private final ConfigUtil configUtil; private final JwtService jwtService; private final PasswordEncoder passwordEncoder; private final AuthenticationManager authenticationManager; + private final AuthRepository authRepository; + + public String loginUser(String username, String password) { + LoginDTO loginRequest = LoginDTO + .builder() + .username(username) + .password(password) + .build(); + AuthenticationResponse response = authenticate(loginRequest); + if (authRepository.findByName(username).isPresent()) { + AuthCodes authCode = authRepository.findByName(username).get(); + authCode.setAuth(response.getToken()); + authRepository.save(authCode); + } else { + AuthCodes authCode = AuthCodes.builder() + .name(username) + .auth(response.getToken()) + .build(); + authRepository.save(authCode); + } + return response.getToken(); + } + + public String logoutUser(HttpServletRequest request) throws Exception { + String token = request.getHeader("X-authorization"); +// Authentication auth = checkAuthentication(token); + // TODO: to get the authentication, we need the inputToken, which means that logout requires the token as a parameter + // Check with Chris if this is the way to go + HttpSession session = request.getSession(); + if (session != null && session.getId() != null) { + session.invalidate(); + authRepository.delete(authRepository.findByName(token).orElseThrow()); + } + return "User logged out successfully"; + } + + /** + * This function, during login, will take in the loginDTO and generate the jwtToken. + * @param loginDTO + * @return + */ + public AuthenticationResponse authenticate(LoginDTO loginDTO) { + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken( + loginDTO.getUsername(), + loginDTO.getPassword() + )); + var user = userRepository.findByUsername(loginDTO.getUsername()) + .orElseThrow(); + var jwtToken = jwtService.generateToken(user); + return AuthenticationResponse + .builder() + .token(jwtToken) + .build(); + } + + /** + * This is the main method to check one's authentication. + * It will check both the security context and the authTokens table to verify the user is logged in. + * + * Currently not used. Eventually will be deprecated out and deleted. + * @param inputToken + * @return + * @throws Exception + */ + //TODO: either delete this or put inside filter when filter is done + public Authentication checkAuthentication(String inputToken) throws Exception { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication instanceof AnonymousAuthenticationToken || authentication == null) return null; + var authCode = authRepository.findByName(authentication.getName()) + .orElseThrow(() -> new RuntimeException("Authentication not found")); // TODO: requires error handling, find out what SBH1 returns + if (inputToken.equals(authCode.getAuth())) { + return authentication; + } else { + throw new Exception("Authentication failed."); + } + } + /** + * Helper function for registering a new user. + * @param userRegistrationDTO + * @return + */ public AuthenticationResponse register(UserRegistrationDTO userRegistrationDTO) { if (!verifyPasswords(userRegistrationDTO.getPassword1(), userRegistrationDTO.getPassword2())) { return AuthenticationResponse @@ -62,11 +149,7 @@ public AuthenticationResponse register(UserRegistrationDTO userRegistrationDTO) .isCurator(false) .build(); user.setGraphUri("https://synbiohub.org/user/" + user.getUsername()); - if (user.getRole().equals(Role.ADMIN)) { - user.setIsAdmin(true); - } else { - user.setIsAdmin(false); - } + user.setIsAdmin(user.getRole().equals(Role.ADMIN)); userRepository.save(user); var jwtToken = jwtService.generateToken(user); return AuthenticationResponse @@ -75,21 +158,12 @@ public AuthenticationResponse register(UserRegistrationDTO userRegistrationDTO) .build(); } - public AuthenticationResponse authenticate(LoginDTO loginDTO) { - authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken( - loginDTO.getUsername(), - loginDTO.getPassword() - )); - var user = userRepository.findByUsername(loginDTO.getUsername()) - .orElseThrow(); - var jwtToken = jwtService.generateToken(user); - return AuthenticationResponse - .builder() - .token(jwtToken) - .build(); - } - + /** + * This function checks if the inputted string is a valid email address. + * + * @param email + * @return + */ public boolean isValidEmail(String email) { String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\."+ @@ -103,17 +177,89 @@ public boolean isValidEmail(String email) return pat.matcher(email).matches(); } - public User getUserProfile() throws CloneNotSupportedException { - Authentication authentication = checkAuthentication(); - if (authentication == null) { + public User getUserProfile() throws Exception { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null) { return null; } - User user = userRepository.findByUsername(authentication.getName()).orElseThrow(); + User user = userRepository.findByUsername(auth.getName()).orElseThrow(); User copyUser = (User) user.clone(); copyUser.setPassword(""); return copyUser; } + public User updateUserProfile(Map allParams) throws Exception { +// Authentication auth = checkAuthentication(inputToken); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + User existingUser = getUserProfile(); + if (existingUser == null || auth == null) { + return null; + } + updateUserFields(existingUser, allParams); + return existingUser; + } + + public String setupInstance(Map allParams) { + String fileName = "config.local.json"; + String workingDirectory = System.getProperty("user.dir") + "/data"; + File file = new File(workingDirectory + File.separator + fileName); + + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + + Map userParams = new HashMap<>(); + UserRegistrationDTO userRegistrationDTO = UserRegistrationDTO + .builder() + .username((String) allParams.get("userName")) + .name((String) allParams.get("userFullName")) + .affiliation((String) allParams.get("affiliation")) + .email((String) allParams.get("userEmail")) + .password1((String) allParams.get("userPassword")) + .password2((String) allParams.get("userPasswordConfirm")) + .build(); + register(userRegistrationDTO); + + allParams.remove("userName"); + allParams.remove("userFullName"); + allParams.remove("affiliation"); + allParams.remove("userEmail"); + allParams.remove("userPassword"); + allParams.remove("userPasswordConfirm"); + + try { + if (file.createNewFile()) { + allParams.put("sparqlEndpoint", "http://virtuoso3:8890/sparql"); + allParams.put("graphStoreEndpoint", "http://virtuoso3:8890/sparql-graph-crud-auth/"); + allParams.put("firstLaunch", false); + allParams.put("version", 1); + Map wor = new HashMap<>(); + wor.put("https://synbiohub.org", "http://localhost:6789"); + allParams.put("webOfRegistries", wor);// TODO: Make sure web of registries is correct + Map themeParams = new HashMap<>(); + themeParams.put("default", allParams.get("color")); + allParams.put("themeParameters", themeParams); + allParams.remove("color"); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.enable(SerializationFeature.INDENT_OUTPUT); + ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter(); + String json = writer.writeValueAsString(allParams); + FileWriter fw = new FileWriter(file.getAbsoluteFile()); + BufferedWriter bw = new BufferedWriter(fw); + bw.write(json); + bw.close(); + ConfigUtil.refreshLocalJson(); + log.info("Setup successful!"); + return "Setup Successful"; + } else { + log.info("Local file already exists. Setup proceeds."); + return "File already exists!"; + } + } catch (IOException e) { + log.error("Setup failed."); + return "Failed to create file!"; + } + } + /** * Compares the user's X-authorization token to the one passed in * @param xauth X-authorization token passed in from HTTP header @@ -147,17 +293,6 @@ public Boolean isOwnedBy(String topLevelUri) throws IOException { return owners.contains(ConfigUtil.get("graphPrefix").asText() + "user/" + SecurityContextHolder.getContext().getAuthentication().getName()); } - public User updateUser(Map allParams) throws AuthenticationException, CloneNotSupportedException { - ObjectMapper mapper = new ObjectMapper(); - Authentication auth = checkAuthentication(); - User existingUser = getUserProfile(); - if (existingUser == null || auth == null) { - return null; - } - updateUserFields(existingUser, allParams); - return existingUser; - } - private void updateUserFields(User user, Map allParams) { if (allParams.get("name") != null) { user.setName(allParams.get("name")); @@ -170,17 +305,6 @@ private void updateUserFields(User user, Map allParams) { } } - public Authentication checkValidLogin(AuthenticationManager authenticationManager, String email, String password) { - Authentication auth; - try { - auth = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(email, password)); - } catch (Exception e) { - return null; - } - return auth; - } - public Map registerNewAdminUser(Map allParams) { Map registerParams = new HashMap<>(); registerParams.put("username", allParams.get("userName")); @@ -193,12 +317,6 @@ public Map registerNewAdminUser(Map allParams) { } - public Authentication checkAuthentication() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication instanceof AnonymousAuthenticationToken || authentication == null) return null; - return authentication; - } - private Boolean verifyPasswords(String password1, String password2) { return password1.equals(password2); } @@ -209,12 +327,12 @@ public User createUser(User user) { public User getUserByUsername(String username) { Optional optionalUser = userRepository.findByUsername(username); - return optionalUser.get(); + return optionalUser.orElse(null); } public User getUserByEmail(String email) { Optional optionalUser = userRepository.findByEmail(email); - return optionalUser.get(); + return optionalUser.orElse(null); } diff --git a/backend/src/main/java/com/synbiohub/sbh3/sparql/GetDatabaseStatus.sparql b/backend/src/main/java/com/synbiohub/sbh3/sparql/GetDatabaseStatus.sparql new file mode 100644 index 00000000..9d39f71b --- /dev/null +++ b/backend/src/main/java/com/synbiohub/sbh3/sparql/GetDatabaseStatus.sparql @@ -0,0 +1,5 @@ +SELECT ?subject ?predicate ?object +WHERE { + ?subject ?predicate ?object +} +LIMIT 5 \ No newline at end of file diff --git a/backend/src/main/java/com/synbiohub/sbh3/utils/ConfigUtil.java b/backend/src/main/java/com/synbiohub/sbh3/utils/ConfigUtil.java index 951a8a0d..5208a480 100644 --- a/backend/src/main/java/com/synbiohub/sbh3/utils/ConfigUtil.java +++ b/backend/src/main/java/com/synbiohub/sbh3/utils/ConfigUtil.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -60,18 +59,13 @@ public static JsonNode getLocaljson() { return localjson; } - public static void set(String key, Object value) { + public static void set(JsonNode rootNode, String key, Object value) { try { - JsonNode valueNode = null; - if (value instanceof Boolean) { - valueNode = JsonNodeFactory.instance.booleanNode((Boolean) value); - } else if (value instanceof String) { - valueNode = JsonNodeFactory.instance.textNode((String) value); - } + ObjectMapper mapper = new ObjectMapper(); + ObjectNode objectNode = (ObjectNode) rootNode; + objectNode.putPOJO(key, value); - if (valueNode != null) { - ((ObjectNode) localjson).set(key, valueNode); - } + mapper.writerWithDefaultPrettyPrinter().writeValue(new File("data/config.local.json"), objectNode); } catch (Exception e) { log.error("Error setting key: " + key + " in local config."); } @@ -86,10 +80,14 @@ public static JsonNode refreshLocalJson() throws IOException { } - public Boolean isLaunched() { + public static Boolean isLaunched() { if (localjson.has("firstLaunch")) { return localjson.get("firstLaunch").asBoolean(); } return json.get("firstLaunch").asBoolean(); } + + public static Boolean checkLocalJson(String fieldName) { + return localjson.has(fieldName); + } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 11d4949d..af695133 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -36,4 +36,9 @@ plugin: jwt: private.key: classpath:app.priv - public.key: classpath:app.pub \ No newline at end of file + public.key: classpath:app.pub + +roleToEndpointPermissions: + USER: ["/profile", "/do_logout", "/submit", "/rootCollections", "/setup", "/login", "/register", "/search", "/search/**", "/searchCount", "/searchCount/**", "/twins", "/uses", "/similar", "/sbol", "/sbolnr", "/metadata", "/gb", "/fasta", "/gff", "/download", "/public/**", "/sparql", "/ComponentDefinition/**", "/**/count", "/count"] + CURATOR: ["/makePublic"] + ADMIN: ['/admin/sparql', '/admin', '/admin/virtuoso', '/admin/graphs', '/admin/log', '/admin/mail', '/admin/plugins', '/admin/savePlugin', '/admin/deletePlugin', '/admin/registries', '/admin/saveRegistry', '/admin/deleteRegistry', '/admin/setAdministratorEmail', '/admin/retrieveFromWebOfRegistries', '/admin/federate', '/admin/remotes', '/admin/saveRemote', '/admin/deleteRemote', '/admin/explorerlog', '/admin/explorer', '/admin/explorerUpdateIndex', '/admin/theme', '/admin/users', '/admin/newUser', '/admin/updateUser', '/admin/deleteUser'] diff --git a/backend/src/main/resources/db/migration/V13__createAuthTokenTable.sql b/backend/src/main/resources/db/migration/V13__createAuthTokenTable.sql new file mode 100644 index 00000000..ef3ca8ee --- /dev/null +++ b/backend/src/main/resources/db/migration/V13__createAuthTokenTable.sql @@ -0,0 +1,8 @@ +drop table if exists Auth cascade; +CREATE TABLE Auth ( + id SERIAL PRIMARY KEY, + name VARCHAR(64) NOT NULL, + auth VARCHAR(512) +); + +alter table Auth add constraint Auth_name_unique unique(name); \ No newline at end of file diff --git a/frontend/CUSTOMIZE.md b/frontend/CUSTOMIZE.md index 736d3de7..96dbe9c3 100644 --- a/frontend/CUSTOMIZE.md +++ b/frontend/CUSTOMIZE.md @@ -86,7 +86,7 @@ Defining additional metadata is very similar to defining custom tables (by desig #### Defining Metadata Sections -A section is defined by a JSON object with various properties. These properties are essentially identical to the properties listed in "Defining Table Sections" above. The only thing to be away of is these sections will be rendered differently. Rather than being rendered as a table, these sections will be rendered as individual sections (rows) of displayed metadata in the viewing interface's side panel. +A section is defined by a JSON object with various properties. These properties are essentially identical to the properties listed in "Defining Table Sections" above. The only thing to be wary of is these sections will be rendered differently. Rather than being rendered as a table, these sections will be rendered as individual sections (rows) of displayed metadata in the viewing interface's side panel. **Important Note: For metadata, some of the properties in a table's section object aren't necessary/do nothing. These properties include icon and infoLink.** diff --git a/frontend/components/Viewing/PageJSON/Parsing/compileFile.js b/frontend/components/Viewing/PageJSON/Parsing/compileFile.js new file mode 100644 index 00000000..234b1e9a --- /dev/null +++ b/frontend/components/Viewing/PageJSON/Parsing/compileFile.js @@ -0,0 +1,13 @@ +export function compileFile(json) { + json?.tables?.forEach(table => { + table.sections?.forEach(section => { + if (section?.predicates) { + section.predicates.forEach(predicate => { + if (predicate.includes('||')) { + console.log(section, predicate.split('||')); + } + }); + } + }); + }); +} diff --git a/frontend/components/Viewing/PageJSON/Parsing/parseTableHeaders.js b/frontend/components/Viewing/PageJSON/Parsing/parseTableHeaders.js new file mode 100644 index 00000000..9805ac70 --- /dev/null +++ b/frontend/components/Viewing/PageJSON/Parsing/parseTableHeaders.js @@ -0,0 +1,11 @@ +export function parseTableHeaders(sections) { + const headers = []; + sections?.forEach(section => { + section.title = section.title?.split('__')[0]; + if (!headers.includes(section) && section) { + headers.push(section); + } + }); + + return headers; +} diff --git a/frontend/components/Viewing/PageJSON/Rendering/GenericContent.js b/frontend/components/Viewing/PageJSON/Rendering/GenericContent.js index 0463d045..66372873 100644 --- a/frontend/components/Viewing/PageJSON/Rendering/GenericContent.js +++ b/frontend/components/Viewing/PageJSON/Rendering/GenericContent.js @@ -5,10 +5,12 @@ import { Fragment } from 'react'; import TableBuilder from './TableBuilder'; import CustomComponents from '../CustomComponents.js'; +import { compileFile } from '../Parsing/compileFile'; export default function GenericContent({ json, uri, metadata }) { if (metadata) { if (!json || !json.metadata) return null; + compileFile(json); const content = json.metadata.map((metadata, index) => { return ( { let currentSection = sections[key]; const foundData = currentSection.some((possibility, index) => { - const titleKey = possibility.section.title; + const titleKey = possibility.section.title.split('__')[0]; if ( possibility.section && !possibility.section.predicates && @@ -110,26 +110,54 @@ function createKeyToValueMap( ); } } else { + if (map[titleKey] && map[titleKey].value !== '') return true; map[titleKey] = { value: possibility.value, index }; - sectionsToRender.push({ - ...possibility.section, - key: titleKey, - tableIcon: possibility.table.icon - }); + // check if already in sectionsToRender, overwrite if that's the case + const index = sectionsToRender.findIndex( + section => section.key === titleKey + ); + if (index === -1) { + sectionsToRender.push({ + ...possibility.section, + key: titleKey, + tableIcon: possibility.table.icon + }); + } else { + sectionsToRender[index] = { + ...possibility.section, + key: titleKey, + tableIcon: possibility.table.icon + }; + } return true; } }); if (!foundData && currentSection.length > 0) { const titleKey = currentSection[0].section.title; - map[titleKey] = { value: '', index: 0 }; - sectionsToRender.push({ - ...currentSection[0].section, - key: titleKey, - tableIcon: currentSection[0].table.icon - }); + if (!(map[titleKey] && map[titleKey].value !== '')) { + map[titleKey] = { value: '', index: 0 }; + const index = sectionsToRender.findIndex( + section => section.key === titleKey + ); + if (index == -1) { + sectionsToRender.push({ + ...currentSection[0].section, + key: titleKey, + tableIcon: currentSection[0].table.icon + }); + } else { + sectionsToRender[index] = { + ...currentSection[0].section, + key: titleKey, + tableIcon: currentSection[0].table.icon + }; + } + } } }); setSectionsToRender(sectionsToRender); + console.log([...sectionsToRender]); + console.log({ ...map }); Object.keys(map).forEach(key => { map[key].value = loadText(map[key].value, map); }); diff --git a/frontend/components/Viewing/PageJSON/Rendering/TableBuilder.js b/frontend/components/Viewing/PageJSON/Rendering/TableBuilder.js index 47a0760a..ff58cf85 100644 --- a/frontend/components/Viewing/PageJSON/Rendering/TableBuilder.js +++ b/frontend/components/Viewing/PageJSON/Rendering/TableBuilder.js @@ -1,14 +1,13 @@ -import getQueryResponse from '../../../../sparql/tools/getQueryResponse'; import { useEffect, useState } from 'react'; import styles from '../../../../styles/view.module.css'; import Link from 'next/link'; -import SectionRenderer from './SectionRenderer'; import RenderIcon from './RenderIcon'; import MetadataRenderer from './MetadataRenderer'; import parseQueryResult from '../Fetching/parseQueryResult'; import executeQueryFromTableJSON from '../Fetching/executeQueryFromTableJSON'; import RowWrapper from './RowWrapper'; import { useDispatch } from 'react-redux'; +import { parseTableHeaders } from '../Parsing/parseTableHeaders'; /** * This Component renders an individual table based on given JSON @@ -49,8 +48,6 @@ function TableRenderer({ uri, prefixes, table, metadata }) { return ; } - console.log(content); - if (content.length == 0) { return
No columns to display
; } @@ -72,9 +69,14 @@ function TableRenderer({ uri, prefixes, table, metadata }) { } function createHeader(columns, content) { + parseTableHeaders(columns); + + const columnsProcessed = {}; + return columns .map((column, index) => { - if (column.title && !column.hide) { + if (column.title && !column.hide && !columnsProcessed[column.title]) { + columnsProcessed[column.title] = true; const customInfoLink = content && content.length > 0 && diff --git a/frontend/components/Viewing/PageJSON/Types/ComponentDefinition.json b/frontend/components/Viewing/PageJSON/Types/ComponentDefinition.json index 6bb53d98..33bf70d4 100644 --- a/frontend/components/Viewing/PageJSON/Types/ComponentDefinition.json +++ b/frontend/components/Viewing/PageJSON/Types/ComponentDefinition.json @@ -245,4 +245,159 @@ "$TABLES[Members of these Collections]", "$TABLES[Attachments]" ] -} \ No newline at end of file +} +======= + "type": "http://sbols.org/v2#ComponentDefintion", + "prefixes": [ + "PREFIX rdf: ", + "PREFIX dcterms: ", + "PREFIX dc: ", + "PREFIX sbh: ", + "PREFIX prov: ", + "PREFIX sbol: ", + "PREFIX xsd: ", + "PREFIX rdfs: ", + "PREFIX purl: " + ], + "metadata": [ + { + "title": "Type", + "rootPredicate": "sbol:type", + "icon": "faVials", + "sections": [ + { + "title": "Extra Work", + "stripAfter": "#", + "predicates": [] + } + ] + }, + { + "title": "Role(s)", + "rootPredicate": "sbol:role", + "icon": "faVials", + "sections": [ + { + "title": "Extra Work", + "stripAfter": "#", + "predicates": [] + } + ] + } + ], + "tables": [ + { + "icon": "faPuzzlePiece", + "title": "Components", + "rootPredicate": "sbol:component", + "sections": [ + { + "title": "ComponentLink", + "hide": true, + "predicates": ["sbol:definition"] + }, + { + "title": "InstanceLink", + "hide": true, + "predicates": [] + }, + { + "title": "Access", + "infoLink": "https://dissys.github.io/sbol-owl/sbol-owl.html#access", + "predicates": ["sbol:access"], + "stripAfter": "#" + }, + { + "title": "Instance", + "link": "$", + "infoLink": "http://sbols.org/v2#component", + "predicates": ["dcterms:title"] + }, + { + "title": "Definition", + "infoLink": "https://dissys.github.io/sbol-owl/sbol-owl.html#definition", + "link": "$", + "predicates": ["sbol:definition", "dcterms:title"] + } + ] + }, + { + "icon": "faDna", + "title": "Sequence Annotations", + "rootPredicate": "sbol:sequenceAnnotation", + "orderBy": "xsd:integer(?start)", + "sections": [ + { + "title": "Sequence Annotation Link", + "predicates": [], + "hide": true + }, + { + "title": "Sequence Annotation", + "predicates": ["dcterms:title"], + "icon": "faInfoCircle", + "link": "$", + "infoLink": "http://sbols.org/v2#SequenceAnnotation", + "info": "Learn more about Sequence Annotations" + }, + { + "title": "Location", + "text": "$, $", + "link": "$", + "infoLink": "http://sbols.org/v2#Location" + }, + { + "title": "Start", + "icon": "faInfoCircle", + "predicates": ["sbol:location", "sbol:start"], + "infoLink": "http://sbols.org/v2#Location", + "info": "Learn more about Locations", + "hide": true + }, + { + "title": "End", + "icon": "faInfoCircle", + "predicates": ["sbol:location", "sbol:end"], + "infoLink": "http://sbols.org/v2#Location", + "info": "Learn more about Locations", + "hide": true + }, + { + "title": "Annotation Link", + "predicates": ["sbol:location", "sbol:persistentIdentity"], + "hide": true + }, + { + "title": "ComponentLink", + "hide": true, + "predicates": ["sbol:component", "sbol:definition"] + }, + { + "title": "Component", + "infoLink": "http://sbols.org/v2#component", + "link": "$", + "predicates": ["sbol:component", "sbol:definition", "dcterms:title"] + }, + { + "title": "Role(s)", + "group": true, + "predicates": ["sbol:role"], + "icon": "faInfoCircle", + "link": "search/role=<$>&", + "linkType": "search", + "infoLink": "http://sbols.org/v2#role", + "info": "Learn more about roles", + "stripAfter": "#" + } + ] + } + ], + "pages": [ + "Details", + "$TABLES[Components]", + "$TABLES[Sequence Annotations]", + "Other Properties", + "Member of these Collections", + "Attachments" + ] +} diff --git a/tests/README.md b/tests/README.md index 5e375399..9cf49e4e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -5,8 +5,9 @@ Steps to run the test suite using Docker 1. Download Docker Desktop 2. Downlad tests folder and open in terminal -3. cd .. -4. tests/test.sh +3. In terminal: `cd ..` +4. Pull the updated docker image, in terminal: `docker pull synbiohub/sbh3backend:snapshot` +5. In terminal: `tests/test.sh` ## Running the test suite manually @@ -20,8 +21,11 @@ Ubuntu:\ Then build a docker image from the local version of synbiohub using `docker build -t synbiohub/synbiohub:snapshot-standalone -f docker/Dockerfile .` +Build a docker image of synbiohub3 backend using +`docker build ./backend --tag synbiohub/sbh3backend:snapshot` (need to test) + Finally, run the test suite using -`bash tests/test3.sh` +`bash tests/test.sh` ## Writing new tests diff --git a/tests/__pycache__/TestState.cpython-39.pyc b/tests/__pycache__/TestState.cpython-39.pyc index a6bbb445..b19abe3d 100644 Binary files a/tests/__pycache__/TestState.cpython-39.pyc and b/tests/__pycache__/TestState.cpython-39.pyc differ diff --git a/tests/__pycache__/first_time_setup.cpython-39.pyc b/tests/__pycache__/first_time_setup.cpython-39.pyc index 5d36eb58..bfb54645 100644 Binary files a/tests/__pycache__/first_time_setup.cpython-39.pyc and b/tests/__pycache__/first_time_setup.cpython-39.pyc differ diff --git a/tests/__pycache__/test_admin.cpython-39.pyc b/tests/__pycache__/test_admin.cpython-39.pyc index cab0354c..9b7bab12 100644 Binary files a/tests/__pycache__/test_admin.cpython-39.pyc and b/tests/__pycache__/test_admin.cpython-39.pyc differ diff --git a/tests/__pycache__/test_arguments.cpython-39.pyc b/tests/__pycache__/test_arguments.cpython-39.pyc index 7aaf533f..5a31e446 100644 Binary files a/tests/__pycache__/test_arguments.cpython-39.pyc and b/tests/__pycache__/test_arguments.cpython-39.pyc differ diff --git a/tests/__pycache__/test_download.cpython-39.pyc b/tests/__pycache__/test_download.cpython-39.pyc index 13f605d2..1f98103f 100644 Binary files a/tests/__pycache__/test_download.cpython-39.pyc and b/tests/__pycache__/test_download.cpython-39.pyc differ diff --git a/tests/__pycache__/test_functions.cpython-39.pyc b/tests/__pycache__/test_functions.cpython-39.pyc index 6072a5f8..04ec31a0 100644 Binary files a/tests/__pycache__/test_functions.cpython-39.pyc and b/tests/__pycache__/test_functions.cpython-39.pyc differ diff --git a/tests/__pycache__/test_root.cpython-39.pyc b/tests/__pycache__/test_root.cpython-39.pyc index e5fe2669..32c686d6 100644 Binary files a/tests/__pycache__/test_root.cpython-39.pyc and b/tests/__pycache__/test_root.cpython-39.pyc differ diff --git a/tests/__pycache__/test_search.cpython-39.pyc b/tests/__pycache__/test_search.cpython-39.pyc index acad7ab3..2f62bd47 100644 Binary files a/tests/__pycache__/test_search.cpython-39.pyc and b/tests/__pycache__/test_search.cpython-39.pyc differ diff --git a/tests/__pycache__/test_tests.cpython-39.pyc b/tests/__pycache__/test_tests.cpython-39.pyc index 3d8f6e27..8bec247f 100644 Binary files a/tests/__pycache__/test_tests.cpython-39.pyc and b/tests/__pycache__/test_tests.cpython-39.pyc differ diff --git a/tests/__pycache__/test_twins.cpython-39.pyc b/tests/__pycache__/test_twins.cpython-39.pyc index f352647c..822ae565 100644 Binary files a/tests/__pycache__/test_twins.cpython-39.pyc and b/tests/__pycache__/test_twins.cpython-39.pyc differ diff --git a/tests/__pycache__/test_user.cpython-39.pyc b/tests/__pycache__/test_user.cpython-39.pyc index 8e84cc73..a07fd8fe 100644 Binary files a/tests/__pycache__/test_user.cpython-39.pyc and b/tests/__pycache__/test_user.cpython-39.pyc differ diff --git a/tests/first_time_setup.py b/tests/first_time_setup.py index 65af1d09..6f380330 100644 --- a/tests/first_time_setup.py +++ b/tests/first_time_setup.py @@ -1,5 +1,5 @@ from unittest import TestCase -from test_functions import compare_get_request, compare_post_request, post_request, post_json_request, compare_post_json_request +from test_functions import compare_get_request, compare_post_request, post_request, post_json_request from test_arguments import test_print @@ -36,11 +36,11 @@ def test_post(self): } #Use to do /setup and test - compare_post_json_request('setup', setup, headers = {"Accept": "text/plain"}, route_parameters = [], files = None) + # compare_post_json_request('setup', setup, headers = {"Accept": "text/plain"}, route_parameters = [], files = None) #Use to do /setup "without" comparing the responses - #post_json_request("setup", 1, setup, headers = {"Accept": "text/plain", "Content-Type": "application/json"}, route_parameters = [], files = None) - #post_json_request("setup", 3, setup, headers = {"Accept": "text/plain", "Content-Type": "application/json"}, route_parameters = [], files = None) + post_json_request("setup", 1, setup, headers = {"Accept": "text/plain", "Content-Type": "application/json"}, route_parameters = [], files = None) + post_json_request("setup", 3, setup, headers = {"Accept": "text/plain", "Content-Type": "application/json"}, route_parameters = [], files = None) test_print("test_setup_post completed") diff --git a/tests/test_admin.py b/tests/test_admin.py index d713c766..6ad96cf6 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -1,7 +1,7 @@ import os from test_arguments import test_print from unittest import TestCase -from test_functions import compare_get_request, compare_post_request, compare_get_request_json, compare_get_request_json_list, login_with +from test_functions import compare_get_request, compare_post_request, login_with class TestAdmin(TestCase): @@ -16,30 +16,27 @@ def test_admin1(self): test_print("test_post_login completed") test_print("test_admin_sparql starting") - compare_get_request_json("/admin/sparql?query=:query", headers = {"Accept":"application/json"}, route_parameters = ["SELECT+%3Fsubject+%3Fpredicate+%3Fobject+WHERE+%7B+%3Fsubject+%3Fpredicate+%3Fobject+.+FILTER+%28str%28%3Fobject%29+%3D+%22BBa_B0034%22%29%7D"], test_type = test_type) + compare_get_request("/admin/sparql?query=:query", headers = {"Accept":"application/json"}, route_parameters = ["SELECT+%3Fsubject+%3Fpredicate+%3Fobject+WHERE+%7B+%3Fsubject+%3Fpredicate+%3Fobject+.+FILTER+%28str%28%3Fobject%29+%3D+%22BBa_B0034%22%29%7D"], test_type = test_type, comparison_type="json") test_print("test_admin_sparql completed") test_print("test_admin_status starting") - compare_get_request_json("/admin", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["instanceName", "defaultGraph", "graphPrefix"]) + compare_get_request("/admin", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="json", fields=["instanceName", "defaultGraph", "graphPrefix"]) test_print("test_admin_status completed") - # test_print("test_admin_virtuoso starting") - # compare_get_request("/admin/virtuoso", headers = {"Accept":"text/plain"}, test_type = test_type) - # test_print("test_admin_virtuoso completed") + test_print("test_admin_virtuoso starting") + compare_get_request("/admin/virtuoso", headers = {"Accept":"text/plain"}, test_type = test_type) + test_print("test_admin_virtuoso completed") # test_print("test_admin_graphs starting") - # #will throw error until we get a response from SBH3 - # #compare_get_request_json_list("/admin/graphs", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["graphUri", "numTriples"]) + # compare_get_request("/admin/graphs", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="jsonlist", fields=["graphUri", "numTriples"], key='graphUri') # test_print("test_admin_graphs completed") # test_print("test_admin_log starting") - # #will throw error until we get a response from SBH3 - # #compare_get_request_json_list("admin/log", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["level", "line"]) + # compare_get_request("admin/log", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="jsonlist", fields=["level", "line"], key='line') # test_print("test_admin_log completed") # test_print("test_admin_mail starting") - # #will throw error until we get a response from SBH3 - # #compare_get_request_json("/admin/mail", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["sendGridApiKey", "sendGridFromEmail"]) + # compare_get_request("/admin/mail", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="json", fields=["sendGridApiKey", "sendGridFromEmail"]) # test_print("test_admin_mail completed") # test_print("test_post_admin_mail starting") @@ -51,34 +48,34 @@ def test_admin1(self): # test_print("test_post_admin_mail completed") test_print("test_admin_plugins starting") - compare_get_request_json("/admin/plugins", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["rendering", "download", "submit"]) + compare_get_request("/admin/plugins", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="json", fields=["rendering", "download", "submit"]) test_print("test_admin_plgins completed") - # test_print("test_admin_savePlugin starting") - # data={ - # 'id': 'New', - # 'category' : 'download', - # 'name' : 'test_plugin', - # 'url' : 'jimmy', - # } - # compare_post_request("/admin/savePlugin", data, headers = {"Accept": "text/plain"}, test_name = "admin_savePlugin", test_type = test_type) - # test_print("test_admin_savePlugin completed") + test_print("test_admin_savePlugin starting") + data={ + 'id': 'New', + 'category' : 'download', + 'name' : 'test_plugin', + 'url' : 'jimmy', + } + compare_post_request("/admin/savePlugin", data, headers = {"Accept": "text/plain"}, test_name = "admin_savePlugin", test_type = test_type) + test_print("test_admin_savePlugin completed") - # test_print("test_admin_plugins starting") - # compare_get_request_json("/admin/plugins", headers = {"Accept":"text/plain"}, test_name="testPluginAfterSave", test_type = test_type, fields=["rendering", "download", "submit"]) - # test_print("test_admin_plgins completed") + test_print("test_admin_plugins starting") + compare_get_request("/admin/plugins", headers = {"Accept":"text/plain"}, test_name="testPluginAfterSave", test_type = test_type, comparison_type="json", fields=["rendering", "download", "submit"]) + test_print("test_admin_plgins completed") - # test_print("test_admin_deletePlugin starting") - # data={ - # 'id': '1', - # 'category' : 'download', - # } - # compare_post_request("/admin/deletePlugin", data, headers = {"Accept": "text/plain"}, test_name = "admin_deletePlugin", test_type = test_type) - # test_print("test_admin_deletePlugin completed") + test_print("test_admin_deletePlugin starting") + data={ + 'id': '1', + 'category' : 'download', + } + compare_post_request("/admin/deletePlugin", data, headers = {"Accept": "text/plain"}, test_name = "admin_deletePlugin", test_type = test_type) + test_print("test_admin_deletePlugin completed") # test_print("test_admin_registries starting") - # #will throw error until we get a response from SBH3 - # #compare_get_request_json("admin/registries", headers = {"Accept": "text/plain"}, test_type = test_type, fields=["registries", "errors"]) + # #SBH3 throws error + # compare_get_request("admin/registries", headers = {"Accept": "text/plain"}, test_type = test_type, comparison_type="json", fields=["registries", "errors"]) # test_print("test_admin_registries completed") # test_print("test_admin_saveRegistry starting") @@ -96,12 +93,12 @@ def test_admin1(self): # compare_post_request("/admin/deleteRegistry", data, headers = {"Accept": "text/plain"}, test_name = "admin_deleteRegistry", test_type = test_type) # test_print("test_admin_deleteRegistry completed") - # test_print("test_admin_setAdministratorEmail starting") - # data={ - # 'administratorEmail': 'test@synbiohub.org', - # } - # compare_post_request("/admin/setAdministratorEmail", data, headers = {"Accept": "text/plain"}, test_name = "admin_setAdministratorEmail", test_type = test_type) - # test_print("test_admin_setAdministratorEmail completed") + test_print("test_admin_setAdministratorEmail starting") + data={ + 'administratorEmail': 'test@synbiohub.org', + } + compare_post_request("/admin/setAdministratorEmail", data, headers = {"Accept": "text/plain"}, test_name = "admin_setAdministratorEmail", test_type = test_type) + test_print("test_admin_setAdministratorEmail completed") # test_print("test_admin_retrieveFromWebOfRegistries starting") # data={ @@ -120,8 +117,7 @@ def test_admin1(self): # test_print("test_admin_federate completed") # test_print("test_admin_remotes starting") - # #will throw error until we get a response from SBH3 - # #compare_get_request_json_list("/admin/remotes", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["remotes", "remoteTypes"]) + # compare_get_request("/admin/remotes", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="json", fields=["remotes", "remoteTypes"]) # test_print("test_admin_remotes completed") # test_print("test_saveRemoteICE starting") @@ -181,15 +177,15 @@ def test_admin1(self): # #TODO: hangs up the code, Need SBOL Explorer ON to test? # # test_print("test_admin_explorerlog starting") - # # compare_get_request_json("/admin/explorerlog", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["instanceName", "frontPageText"]) + # # compare_get_request("/admin/explorerlog", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="json", fields=["instanceName", "frontPageText"]) # # test_print("test_admin_explorerlog completed") - # # Need SBOL Explorer ON to test + # #TODO: Need SBOL Explorer ON to test # test_print("test_admin_explorer starting") # compare_get_request("/admin/explorer", headers = {"Accept": "text/plain"}, test_type = test_type) # test_print("test_admin_explorer completed") - # # Need SBOL Explorer ON to test + # #TODO: Need SBOL Explorer ON to test # test_print("test_admin_status starting") # data={ # 'useSBOLExplorer': 'True', @@ -211,9 +207,9 @@ def test_admin1(self): # compare_post_request("/admin/explorerUpdateIndex", data, headers = {"Accept": "text/plain"}, test_name = "admin_explorerUpdateIndex", test_type = test_type) # test_print("test_explorerUpdateIndex completed") - # test_print("test_admin_theme starting") - # compare_get_request_json("/admin/theme", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["instanceName", "frontPageText"]) - # test_print("test_admin_theme completed") + test_print("test_admin_theme starting") + compare_get_request("/admin/theme", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="json", fields=["instanceName", "frontPageText"]) + test_print("test_admin_theme completed") # test_print("test_admin_updateTheme starting") # logo = os.path.basename('./logo.jpg'); @@ -230,8 +226,7 @@ def test_admin1(self): # test_print("test_admin_updateTheme completed") # test_print("test_get_admin_users starting") - # #will throw error until we get a response from SBH3 - # #compare_get_request_json("/admin/users", headers = {"Accept":"text/plain"}, test_type = test_type, fields=["users", "graphUri", "isAdmin"]) + # compare_get_request("/admin/users", headers = {"Accept":"text/plain"}, test_type = test_type, comparison_type="json", fields=["users", "graphUri", "isAdmin"]) # test_print("test_get_admin_users completed") # test_print("test_post_admin_users starting") diff --git a/tests/test_functions.py b/tests/test_functions.py index 2372d8e4..69434baa 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -3,6 +3,7 @@ import requests_html, difflib, sys, requests, json from bs4 import BeautifulSoup from operator import itemgetter +from json.decoder import JSONDecodeError from test_arguments import args, test_print from TestState import TestState, clip_request @@ -199,6 +200,7 @@ def compare_status_codes(sbh1requestcontent, sbh3requestcontent): print("RESPONSE CODE TEST PASSED: Status Code: " + str(sbh3requestcontent.status_code)) return 1 +#Compare text data, exact match def compare_request(sbh1requestcontent, sbh3requestcontent, request, requesttype, test_type): """ Checks a sbh3 request against a sbh1 request. request is the endpoint requested, such as /setup @@ -223,12 +225,25 @@ def compare_request(sbh1requestcontent, sbh3requestcontent, request, requesttype add_test_results(test_passed, test_type) +#Compare JSON data: {} def compare_json(sbh1requestcontent, sbh3requestcontent, test_type, fields): test_passed = compare_status_codes(sbh1requestcontent, sbh3requestcontent) - sbh1_json = json.loads(sbh1requestcontent.text) - sbh3_json = json.loads(sbh3requestcontent.text) + try: + sbh1_json = json.loads(sbh1requestcontent.text) + except JSONDecodeError as e: + sbh1_json = [] + try: + sbh3_json = json.loads(sbh3requestcontent.text) + except JSONDecodeError as e: + sbh3_json = [] + if(sbh1_json != [] and sbh3_json == []): + test_passed = 0 + raise Exception("RESPONSE CONTENT TEST FAILED: Content does not match\n") + if(sbh1_json == [] and sbh3_json != []): + test_passed = 0 + raise Exception("RESPONSE CONTENT TEST FAILED: Content does not match\n") if(fields == []): if(sorted(sbh1_json) != sorted(sbh3_json)): test_passed = 0 @@ -242,14 +257,20 @@ def compare_json(sbh1requestcontent, sbh3requestcontent, test_type, fields): add_test_results(test_passed, test_type) -def compare_json_list(sbh1requestcontent, sbh3requestcontent, test_type, fields): +#Compare a list of JSON data: [{}] +def compare_json_list(sbh1requestcontent, sbh3requestcontent, test_type, fields, key): test_passed = compare_status_codes(sbh1requestcontent, sbh3requestcontent) - - sbh1resultlist = json.loads(sbh1requestcontent.text) - sbh3resultlist = json.loads(sbh3requestcontent.text) - sorted_sbh1_list = sorted(sbh1resultlist, key=itemgetter('uri')) - sorted_sbh3_list = sorted(sbh3resultlist, key=itemgetter('uri')) + try: + sbh1resultlist = json.loads(sbh1requestcontent.text) + except JSONDecodeError as e: + sbh1resultlist = [] + try: + sbh3resultlist = json.loads(sbh3requestcontent.text) + except JSONDecodeError as e: + sbh3resultlist = [] + sorted_sbh1_list = sorted(sbh1resultlist, key=itemgetter(key)) + sorted_sbh3_list = sorted(sbh3resultlist, key=itemgetter(key)) if(len(sorted_sbh1_list) != len(sorted_sbh3_list)): test_passed = 0 raise Exception("RESPONSE CONTENT TEST FAILED: Content does not match\n") @@ -355,32 +376,17 @@ def login_with(data, valid, headers = {'Accept':'text/plain'}): raise Exception("RESPONSE CONTENT TEST FAILED: Content does not match\n") print("RESPONSE CODE TEST PASSED: Status Code: " + str(resultSBH3.status_code)) -def compare_get_request(request, test_name = "", route_parameters = [], headers = {}, test_type="Other"): +def compare_get_request(request, test_name = "", route_parameters = [], headers = {}, test_type="Other", comparison_type="text", fields = [], key=''): """Complete a get request and error if it differs from previous results. page request -- string, the name of the page being requested - route_parameters -- a ordered lists of the parameters for the endpoint test_name -- a name to make the request unique from another test of this endpoint - headers -- a dictionary of headers to include in the request - re_render_time -- time to wait in milliseconds before rendering javascript again""" - - # remove any leading forward slashes for consistency - request = clip_request(request) - - #gives filepath for old test for 1 - sbh3 test: now using for checking which endpoints were tested - testpath = request_file_path(request, "get request", test_name) - test_state.add_get_request(request, testpath, test_name) - #get_request("profile", 1, headers = {"Accept": "text/plain"}, route_parameters = [], re_render_time = 0) - compare_request(get_request(request, 1, headers, route_parameters), get_request(request, 3, headers, route_parameters), request, "get request", test_type) - -def compare_get_request_json(request, test_name = "", route_parameters = [], headers = {}, test_type="Other", fields = []): - """Complete a get request and error if the json fields differs from previous results. -page - request -- string, the name of the page being requested route_parameters -- a ordered lists of the parameters for the endpoint - test_name -- a name to make the request unique from another test of this endpoint headers -- a dictionary of headers to include in the request - re_render_time -- time to wait in milliseconds before rendering javascript again""" + test_type -- string, the type/category of the endpoint based on the api docs + comparison_type -- string, the type of comparison + fields -- list of strings, specify fields to compare for json and json list comparisons if needed + key -- string, key to sort by for json list comparison""" # remove any leading forward slashes for consistency request = clip_request(request) @@ -389,25 +395,12 @@ def compare_get_request_json(request, test_name = "", route_parameters = [], hea testpath = request_file_path(request, "get request", test_name) test_state.add_get_request(request, testpath, test_name) #get_request("profile", 1, headers = {"Accept": "text/plain"}, route_parameters = [], re_render_time = 0) - compare_json(get_request(request, 1, headers, route_parameters), get_request(request, 3, headers, route_parameters), test_type, fields) - -def compare_get_request_json_list(request, test_name = "", route_parameters = [], headers = {}, test_type="Other", fields = []): - """Complete a get request and error if the json fields differs from previous results. -page - request -- string, the name of the page being requested - route_parameters -- a ordered lists of the parameters for the endpoint - test_name -- a name to make the request unique from another test of this endpoint - headers -- a dictionary of headers to include in the request - re_render_time -- time to wait in milliseconds before rendering javascript again""" - - # remove any leading forward slashes for consistency - request = clip_request(request) - - #gives filepath for old test for 1 - sbh3 test: now using for checking which endpoints were tested - testpath = request_file_path(request, "get request", test_name) - test_state.add_get_request(request, testpath, test_name) - #get_request("profile", 1, headers = {"Accept": "text/plain"}, route_parameters = [], re_render_time = 0) - compare_json_list(get_request(request, 1, headers, route_parameters), get_request(request, 3, headers, route_parameters), test_type, fields) + if(comparison_type == "text"): + compare_request(get_request(request, 1, headers, route_parameters), get_request(request, 3, headers, route_parameters), request, "get request", test_type) + if(comparison_type == "json"): + compare_json(get_request(request, 1, headers, route_parameters), get_request(request, 3, headers, route_parameters), test_type, fields) + if(comparison_type == "jsonlist"): + compare_json_list(get_request(request, 1, headers, route_parameters), get_request(request, 3, headers, route_parameters), test_type, fields, key) def compare_get_request_download(request, test_name = "", route_parameters = [], headers = {}, test_type="Other"): """Complete a get_file request and error if it differs from previous results. @@ -416,7 +409,7 @@ def compare_get_request_download(request, test_name = "", route_parameters = [], route_parameters -- a ordered lists of the parameters for the endpoint test_name -- a name to make the request unique from another test of this endpoint headers -- a dictionary of headers to include in the request - re_render_time -- time to wait in milliseconds before rendering javascript again""" + test_type -- string, the type/category of the endpoint based on the api docs""" # remove any leading forward slashes for consistency request = clip_request(request) @@ -426,29 +419,17 @@ def compare_get_request_download(request, test_name = "", route_parameters = [], compare_request(get_request_download(request, headers, route_parameters, 1), get_request_download(request, headers, route_parameters, 3), request, "get_file request", test_type) -def compare_post_request(request, data, test_name = "", route_parameters = [], headers = {}, files = None, test_type = "Other"): - """Complete a post request and error if it differs from previous results. - - request-- string, the name of the page being requested - data -- data to send in the post request - route_parameters -- a list of parameters for the url endpoint - test_name -- a name for the test to make multiple tests for the same endpoint unique""" - - # remove any leading forward slashes for consistency - request = clip_request(request) - - testpath = request_file_path(request, "post request", test_name) - test_state.add_post_request(request, testpath, test_name) - - compare_request(post_request(request, 1, data, headers, route_parameters, files = files), post_request(request, 3, data, headers, route_parameters, files = files), request, "post request", test_type) - -def compare_post_json_request(request, data, test_name = "", route_parameters = [], headers = {}, files = None, test_type = "Other"): +def compare_post_request(request, data, test_name = "", route_parameters = [], headers = {}, files = None, test_type = "Other", comparison_type="text"): """Complete a post request and error if it differs from previous results. - request-- string, the name of the page being requested + request -- string, the name of the page being requested data -- data to send in the post request - route_parameters -- a list of parameters for the url endpoint - test_name -- a name for the test to make multiple tests for the same endpoint unique""" + test_name -- a name to make the request unique from another test of this endpoint + route_parameters -- a ordered lists of the parameters for the endpoint + headers -- a dictionary of headers to include in the request + files -- path of file + test_type -- string, the type/category of the endpoint based on the api docs + comparison_type -- string, the type of comparison""" # remove any leading forward slashes for consistency request = clip_request(request) @@ -456,8 +437,10 @@ def compare_post_json_request(request, data, test_name = "", route_parameters = testpath = request_file_path(request, "post request", test_name) test_state.add_post_request(request, testpath, test_name) - compare_request(post_request(request, 1, data, headers, route_parameters, files = files), post_json_request(request, 3, data, headers, route_parameters, files = files), request, "post request", test_type) - + if(comparison_type == "text"): + compare_request(post_request(request, 1, data, headers, route_parameters, files = files), post_request(request, 3, data, headers, route_parameters, files = files), request, "post request", test_type) + if(comparison_type == "json"): + compare_request(post_request(request, 1, data, headers, route_parameters, files = files), post_json_request(request, 3, data, headers, route_parameters, files = files), request, "post request", test_type) # TODO: make checking throw an error when all endpoints are not checked, instead of printing a warning. def cleanup_check(): diff --git a/tests/test_search.py b/tests/test_search.py index fb63d2bd..76fccf80 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -1,6 +1,6 @@ import requests from unittest import TestCase -from test_functions import compare_get_request, compare_post_request, get_request, post_request, compare_get_request_json, compare_get_request_json_list +from test_functions import compare_get_request, compare_post_request, get_request, post_request from test_arguments import test_print # "/manage" is tested within test_submit.py @@ -14,7 +14,7 @@ def test_search(self): # test_searchQuery(self): test_print("test_search starting") # #compare_get_request("/search/:query?", route_parameters = ["I0462"]) - compare_get_request_json_list("/search/:query?", headers = headers, route_parameters = ["BBa_B00"], test_type = test_type, fields=["uri", "displayId", "version", "name", "description", "type"]) + compare_get_request("/search/:query?", headers = headers, route_parameters = ["BBa_B00"], test_type = test_type, comparison_type="jsonlist", fields=["uri", "displayId", "version", "name", "description", "type"], key='uri') test_print("test_search completed") # test_searchCount(self): @@ -30,18 +30,18 @@ def test_search(self): # #test_sparql(self): test_print("test_sparql starting") - compare_get_request_json("/sparql?query=:query", headers = {"Accept":"application/json"}, route_parameters = ["SELECT+%3Fsubject+%3Fpredicate+%3Fobject+WHERE+%7B+%3Fsubject+%3Fpredicate+%3Fobject+.+FILTER+%28str%28%3Fobject%29+%3D+%22BBa_B0034%22%29%7D"]) + compare_get_request("/sparql?query=:query", headers = {"Accept":"application/json"}, route_parameters = ["SELECT+%3Fsubject+%3Fpredicate+%3Fobject+WHERE+%7B+%3Fsubject+%3Fpredicate+%3Fobject+.+FILTER+%28str%28%3Fobject%29+%3D+%22BBa_B0034%22%29%7D"], test_type = test_type, comparison_type="json") test_print("test_sparql completed") test_print("test_subcollections_public starting") # need user - compare_get_request("/public/:collectionId/:displayId/:version/subCollections", route_parameters = ["testid1","testid1_collection", "1"], headers = {"Accept":"text/plain"}, test_type = test_type) - compare_get_request_json_list("/public/:collectionId/:displayId/:version/subCollections", route_parameters = ["igem","categories", "1"], headers = {"Accept":"text/plain"}, test_name="subCollectionsCategories", test_type = test_type, fields=["uri", "displayId", "version", "name", "description"]) + compare_get_request("/public/:collectionId/:displayId/:version/subCollections", route_parameters = ["igem","categories", "1"], headers = {"Accept":"text/plain"}, test_name="subCollectionsCategories", test_type = test_type, comparison_type="jsonlist", fields=["uri", "displayId", "version", "name", "description"], key='uri') test_print("test_subcollections_public completed") test_print("test_uses starting") # # need user - compare_get_request("user/:userId/:collectionId/:displayId/:version/uses", route_parameters = ["testuser1","testid2", "BBa_B0015", "1"],headers = {"Accept": "text/html"}, test_type = test_type) # # need accounts to work first - compare_get_request("public/:collectionId/:displayId/:version/uses", route_parameters = ["testid2", "BBa_B0015", "1"],headers = {"Accept": "text/html"}, test_type = test_type) - compare_get_request_json_list("public/:collectionId/:displayId/:version/uses", test_name = "uses2", route_parameters = ["igem", "BBa_B0034", "1"],headers = {"Accept": "text/plain"}, test_type = test_type, fields=["uri", "displayId", "version", "name", "description", "type"]) + compare_get_request("public/:collectionId/:displayId/:version/uses", test_name = "uses2", route_parameters = ["igem", "BBa_B0034", "1"],headers = {"Accept": "text/plain"}, test_type = test_type, comparison_type="jsonlist", fields=["uri", "displayId", "version", "name", "description", "type"], key='uri') test_print("test_uses completed") test_print("test_count starting") diff --git a/tests/test_twins.py b/tests/test_twins.py index e1ec1c31..0ff98afc 100644 --- a/tests/test_twins.py +++ b/tests/test_twins.py @@ -1,7 +1,7 @@ import requests from unittest import TestCase from test_arguments import test_print -from test_functions import compare_get_request, compare_get_request_json_list +from test_functions import compare_get_request class TestTwins(TestCase): def test_twins(self): @@ -23,6 +23,6 @@ def test_twins(self): # won't work till submit is done - compare_get_request(":userId/:collectionId/:displayId/:version/twins", test_name = "twins1", headers = {"Accept": "text/plain"}, route_parameters = ["testuser1","igem","BBa_B0034","1"]) # when submit is done - compare_get_request(":userId/:collectionId/:displayId/:version/twins", headers = {"Accept": "text/plain"}, route_parameters = ["public","igem","BBa_B0034","1"]) - compare_get_request_json_list("public/:collectionId/:displayId/:version/twins", headers = {"Accept": "text/plain"}, route_parameters = ["igem","BBa_B0034","1"], test_type = test_type, fields=["uri", "displayId", "version", "name", "description", "type"]) + compare_get_request("public/:collectionId/:displayId/:version/twins", headers = {"Accept": "text/plain"}, route_parameters = ["igem","BBa_B0034","1"], test_type = test_type, comparison_type="jsonlist", fields=["uri", "displayId", "version", "name", "description", "type"], key='uri') test_print("test_twins completed") diff --git a/tests/test_user.py b/tests/test_user.py index 29bd954c..e7582c2e 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,6 +1,6 @@ from unittest import TestCase from test_arguments import test_print -from test_functions import compare_post_request, compare_get_request, login_with, post_request, get_request, compare_get_request_json +from test_functions import compare_post_request, compare_get_request, login_with class TestUser(TestCase): @@ -20,22 +20,25 @@ def test_post_register(self): compare_post_request("register", data, test_name = "register1", headers = headers, route_parameters = [], files = None, test_type = test_type) #error - account already in use? - FAIL CASE for 1 - #logininfo = {'email' : 'test2@user.synbiohub', - #'password' : 'test1'} - #login_with(logininfo, 1) - #login_with(logininfo, 3) test_print("test_post_login starting") + #not registered user logininfo = {'email' : 'test7@user.synbiohub', 'password' : 'test'} login_with(logininfo, 0) + #bad password + logininfo = {'email' : 'test1@user.synbiohub', + 'password' : 'password'} + login_with(logininfo, 0) + + #correct login logininfo = {'email' : 'test1@user.synbiohub', 'password' : 'test'} login_with(logininfo, 1) test_print("test_post_login completed") test_print("test_post_register starting") - compare_get_request_json("/profile", headers = headers, route_parameters = [], test_type = test_type, fields=["name", "username", "email", "affiliation", "graphUri"]) + compare_get_request("/profile", headers = headers, route_parameters = [], test_type = test_type, comparison_type="json", fields=["name", "username", "email", "affiliation", "graphUri"]) data={ 'name': 'ronnie', @@ -45,7 +48,6 @@ def test_post_register(self): 'password2' : 'test' } - #uncomment when profile works compare_post_request("profile", data, test_name = "profile2", headers = headers, route_parameters = [], files = None, test_type = test_type) #compare_get_request("/logout")