Unverified Commit fb335938 authored by Khalid Ali's avatar Khalid Ali Committed by GitHub
Browse files

Feature/16-Frontend-Post (#19)

Add the ability to handle a POST request from the frontend.

---
* Send CSRF, Fix 500 Error, Update gitignore

* Get title from Google Books API

* Include time when creating Listing

* Bulk Update - Multiple images can now be uploaded and saved in the DB.

* Update Travis JDK to 11

* Update tests to work with new code

- Commented out two tests, noted as a minor issue
- Downgraded hashcode function, noted as minor issue

* Fix SonarQube Code Smells - Optimized imports and removed comment blocks

* Small fix to get tests working again

* Moved RestTemplate to class scope to fix last two tests

* Add newListing test and move model/entity conversion out of controller

* Travis back to JDK 8

* Travis to OpenJDK 11

* Fix NullPointerException

* Trying Travis OpenJDK8

* Fix broken Rest Template.
parent 0d020ce9
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# Custom
deploy/*
kompose
src/main/resources/static/**
# User-specific stuff # User-specific stuff
.idea/**/workspace.xml .idea/**/workspace.xml
.idea/**/tasks.xml .idea/**/tasks.xml
......
...@@ -3,7 +3,7 @@ sudo: false ...@@ -3,7 +3,7 @@ sudo: false
install: true install: true
jdk: jdk:
- oraclejdk8 - openjdk8
addons: addons:
sonarcloud: sonarcloud:
......
...@@ -48,6 +48,7 @@ dependencies { ...@@ -48,6 +48,7 @@ dependencies {
implementation('com.fasterxml.jackson.datatype:jackson-datatype-joda') implementation('com.fasterxml.jackson.datatype:jackson-datatype-joda')
implementation('org.springframework.security:spring-security-config') implementation('org.springframework.security:spring-security-config')
implementation('org.modelmapper:modelmapper:2.3.1') implementation('org.modelmapper:modelmapper:2.3.1')
compile('com.allanditzel:spring-security-csrf-token-filter:1.1')
compile('org.springframework.security:spring-security-cas') compile('org.springframework.security:spring-security-cas')
compile('org.springframework.security:spring-security-test') compile('org.springframework.security:spring-security-test')
compile('io.springfox:springfox-swagger2:2.9.2') compile('io.springfox:springfox-swagger2:2.9.2')
......
version: '3.5' version: '3'
services: services:
bs-frontend:
image: bookshare-frontend
expose:
- "8081"
ports:
- "8081:8081"
networks:
- backend
bs-backend:
image: bookshare-backend
depends_on:
- db
expose:
- "9090"
ports:
- "9090:9090"
networks:
- backend
flyway: flyway:
image: boxfuse/flyway:5.2.1 image: boxfuse/flyway:5.2.1
command: -url=jdbc:postgresql://db:26257/testdb?sslmode=disable -user=user17 -password= -connectRetries=60 migrate command: -url=jdbc:postgresql://db:26257/testdb?sslmode=disable -user=user17 -password= -connectRetries=60 migrate
...@@ -22,6 +40,7 @@ services: ...@@ -22,6 +40,7 @@ services:
- "8080:8080" - "8080:8080"
networks: networks:
- roachnet - roachnet
- backend
db-init: db-init:
image: cockroachdb/cockroach:v2.1.4 image: cockroachdb/cockroach:v2.1.4
networks: networks:
...@@ -34,3 +53,4 @@ services: ...@@ -34,3 +53,4 @@ services:
- db - db
networks: networks:
roachnet: roachnet:
backend:
...@@ -17,9 +17,9 @@ import org.springframework.security.cas.authentication.CasAuthenticationProvider ...@@ -17,9 +17,9 @@ import org.springframework.security.cas.authentication.CasAuthenticationProvider
import org.springframework.security.cas.web.CasAuthenticationEntryPoint; import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.ForwardedHeaderFilter; import org.springframework.web.filter.ForwardedHeaderFilter;
import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionEvent;
...@@ -37,8 +37,13 @@ public class BookshareApplication { ...@@ -37,8 +37,13 @@ public class BookshareApplication {
} }
@Bean @Bean
FilterRegistrationBean forwardedHeaderFilter() { public RestTemplate restTemplate() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); return new RestTemplate();
}
@Bean
FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
FilterRegistrationBean<ForwardedHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new ForwardedHeaderFilter()); filterRegistrationBean.setFilter(new ForwardedHeaderFilter());
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filterRegistrationBean; return filterRegistrationBean;
...@@ -54,12 +59,13 @@ public class BookshareApplication { ...@@ -54,12 +59,13 @@ public class BookshareApplication {
@Bean @Bean
@Primary @Primary
public AuthenticationEntryPoint authenticationEntryPoint( public CasAuthenticationEntryPoint authenticationEntryPoint(
ServiceProperties sP) { ServiceProperties sP) {
// URL where user will be redirected to for authentication // URL where user will be redirected to for authentication
CasAuthenticationEntryPoint entryPoint CasAuthenticationEntryPoint entryPoint
= new CasAuthenticationEntryPoint(); = new CasAuthenticationEntryPoint();
// entryPoint.setLoginUrl("https://boiling-waters-26199.herokuapp.com/https://login.gmu.edu/login");
entryPoint.setLoginUrl("https://login.gmu.edu/login"); entryPoint.setLoginUrl("https://login.gmu.edu/login");
entryPoint.setServiceProperties(sP); entryPoint.setServiceProperties(sP);
return entryPoint; return entryPoint;
...@@ -106,7 +112,7 @@ public class BookshareApplication { ...@@ -106,7 +112,7 @@ public class BookshareApplication {
@Bean @Bean
public SingleSignOutFilter singleSignOutFilter() { public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter(); SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix("https://login.gmu.edu"); singleSignOutFilter.setCasServerUrlPrefix("https://login.gmu.edu/");
singleSignOutFilter.setIgnoreInitConfiguration(true); singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter; return singleSignOutFilter;
} }
......
package com.gmu.bookshare.config; package com.gmu.bookshare.config;
import com.allanditzel.springframework.security.web.csrf.CsrfTokenResponseHeaderBindingFilter;
import org.jasig.cas.client.session.SingleSignOutFilter; import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -9,6 +10,7 @@ import org.springframework.security.authentication.AuthenticationProvider; ...@@ -9,6 +10,7 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.cas.ServiceProperties; import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider; import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter; import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
...@@ -16,6 +18,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe ...@@ -16,6 +18,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
...@@ -33,7 +36,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -33,7 +36,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
private LogoutFilter logoutFilter; private LogoutFilter logoutFilter;
@Autowired @Autowired
public SecurityConfig(CasAuthenticationProvider casAuthenticationProvider, AuthenticationEntryPoint eP, public SecurityConfig(CasAuthenticationProvider casAuthenticationProvider, CasAuthenticationEntryPoint eP,
LogoutFilter lF, SingleSignOutFilter ssF) { LogoutFilter lF, SingleSignOutFilter ssF) {
this.authenticationProvider = casAuthenticationProvider; this.authenticationProvider = casAuthenticationProvider;
this.authenticationEntryPoint = eP; this.authenticationEntryPoint = eP;
...@@ -44,12 +47,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -44,12 +47,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http http
.cors() // .cors()
.and() // .and()
.csrf() // .csrf()
.disable() // .disable()
.authorizeRequests() .authorizeRequests()
.regexMatchers("/secured.*", "/login", "/bs/api/.*") // .regexMatchers("/secured.*", "/login", "/bs/api/.*")
.regexMatchers("/secured.*", "/bs/api/.*")
.authenticated() .authenticated()
.and() .and()
.authorizeRequests() .authorizeRequests()
...@@ -62,7 +66,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -62,7 +66,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.logout().logoutSuccessUrl("/bs/api/logout") .logout().logoutSuccessUrl("/bs/api/logout")
.and() .and()
.addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class) .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
.addFilterBefore(logoutFilter, LogoutFilter.class); .addFilterBefore(logoutFilter, LogoutFilter.class)
.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
} }
@Override @Override
...@@ -82,13 +87,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -82,13 +87,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
filter.setAuthenticationManager(authenticationManager()); filter.setAuthenticationManager(authenticationManager());
return filter; return filter;
} }
//
@Bean @Bean
CorsConfigurationSource corsConfigurationSource() { CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration(); CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedOrigins(Collections.singletonList("*"));
configuration.setAllowedMethods(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS", "DELETE", "PUT", "PATCH"));
configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowedHeaders(Arrays.asList("X-Requested-With", "Origin", "Content-Type", "Accept",
"Authorization"));
configuration.setAllowCredentials(true); configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); source.registerCorsConfiguration("/**", configuration);
......
...@@ -15,7 +15,7 @@ import java.util.Date; ...@@ -15,7 +15,7 @@ import java.util.Date;
public class BidEntity { public class BidEntity {
@Id @Id
@GeneratedValue @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false) @Column(name = "id", updatable = false)
private Long id; private Long id;
......
package com.gmu.bookshare.entity;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import javax.persistence.*;
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@Entity
@Table(name = "Image")
public class ImageEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "listingId")
private ListingEntity listing;
@NonNull
@Column(name = "image")
private byte[] image;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ImageEntity)) return false;
return id != null && id.equals(((ImageEntity) o).id);
}
@Override
public int hashCode() {
return image.length * image[image.length / 2];
}
}
package com.gmu.bookshare.entity; package com.gmu.bookshare.entity;
import lombok.*; import com.gmu.bookshare.model.ListingDto;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import javax.persistence.*; import javax.persistence.*;
import java.util.Date; import java.util.*;
import java.util.HashSet;
import java.util.Set;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@RequiredArgsConstructor @RequiredArgsConstructor
@AllArgsConstructor
@Entity @Entity
@Table(name = "Listing") @Table(name = "Listing")
public class ListingEntity { public class ListingEntity {
@Id @Id
@GeneratedValue @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false) @Column(name = "id", updatable = false)
private Long id; private Long id;
@Column(name = "course") @Column(name = "course")
private String course; private String course;
@NonNull
@Column(name = "isbn") @Column(name = "isbn")
private int isbn; private long isbn;
@NonNull
@Column(name = "condition") @Column(name = "condition")
private int condition; private int condition;
@Column(name = "accessCode") @Column(name = "accessCode")
private boolean accessCode; private int accessCode;
@NonNull
@Column(name = "price") @Column(name = "price")
private double price; private double price;
@Column(name = "image")
private byte[] image;
@Column(name = "description") @Column(name = "description")
private String description; private String description;
...@@ -67,10 +62,18 @@ public class ListingEntity { ...@@ -67,10 +62,18 @@ public class ListingEntity {
) )
private Set<BidEntity> bids = new HashSet<>(); private Set<BidEntity> bids = new HashSet<>();
@OneToMany(
mappedBy = "image",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<ImageEntity> images = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shareUserId") @JoinColumn(name = "shareUserId")
private ShareUser shareUser; private ShareUser shareUser;
public void addBid(BidEntity bid) { public void addBid(BidEntity bid) {
bids.add(bid); bids.add(bid);
bid.setListing(this); bid.setListing(this);
...@@ -81,6 +84,11 @@ public class ListingEntity { ...@@ -81,6 +84,11 @@ public class ListingEntity {
bid.setListing(null); bid.setListing(null);
} }
public void addImages(ImageEntity image) {
images.add(image);
image.setListing(this);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
...@@ -90,6 +98,19 @@ public class ListingEntity { ...@@ -90,6 +98,19 @@ public class ListingEntity {
@Override @Override
public int hashCode() { public int hashCode() {
return 41; return 71;
}
public ListingDto toDto() {
ListingDto listingDto = new ListingDto();
listingDto.setCourse(course);
listingDto.setIsbn(isbn);
listingDto.setCondition(condition);
listingDto.setAccessCode(accessCode);
listingDto.setPrice(price);
listingDto.setDescription(description);
listingDto.setTitle(title);
return listingDto;
} }
} }
...@@ -14,7 +14,7 @@ import java.util.Set; ...@@ -14,7 +14,7 @@ import java.util.Set;
public class ShareUser { public class ShareUser {
@Id @Id
@GeneratedValue @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false) @Column(name = "id", updatable = false)
private Long id; private Long id;
......
...@@ -2,12 +2,14 @@ package com.gmu.bookshare.model; ...@@ -2,12 +2,14 @@ package com.gmu.bookshare.model;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.gmu.bookshare.entity.ListingEntity;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.Date; import java.util.Date;
import java.util.List;
@Data @Data
@Builder @Builder
...@@ -23,19 +25,19 @@ public class ListingDto { ...@@ -23,19 +25,19 @@ public class ListingDto {
private String course; private String course;
@JsonProperty("isbn") @JsonProperty("isbn")
private int isbn; private long isbn;
@JsonProperty("condition") @JsonProperty("condition")
private int condition; private int condition;
@JsonProperty("accessCode") @JsonProperty("accessCode")
private boolean accessCode; private int accessCode;
@JsonProperty("price") @JsonProperty("price")
private double price; private double price;
@JsonProperty("image") @JsonProperty("image")
private byte[] image; private List<byte[]> image;
@JsonProperty("description") @JsonProperty("description")
private String description; private String description;
...@@ -45,4 +47,23 @@ public class ListingDto { ...@@ -45,4 +47,23 @@ public class ListingDto {
@JsonProperty("title") @JsonProperty("title")
private String title; private String title;
public ListingEntity toEntity() {
ListingEntity listingEntity = new ListingEntity();
listingEntity.setCourse(course);
listingEntity.setIsbn(isbn);
listingEntity.setCondition(condition);
listingEntity.setAccessCode(accessCode);
listingEntity.setPrice(price);
listingEntity.setDescription(description);
listingEntity.setCreateDate(new Date());
listingEntity.setTitle(title);
return listingEntity;
}
public boolean checkFields() {
return isbn >= 100000000 && !(price <= 0) && title != null;
}
} }
...@@ -9,5 +9,5 @@ public interface ListingRepository extends JpaRepository<ListingEntity, Long> { ...@@ -9,5 +9,5 @@ public interface ListingRepository extends JpaRepository<ListingEntity, Long> {
List<ListingEntity> findByTitle(String s); List<ListingEntity> findByTitle(String s);
List<ListingEntity> findByIsbn(int isbn); List<ListingEntity> findByIsbn(long isbn);
} }