diff --git a/src/main/java/com/hithomelabs/CFTunnels/Config/CustomOidcUserConfiguration.java b/src/main/java/com/hithomelabs/CFTunnels/Config/CustomOidcUserConfiguration.java index 22a4e64..1d06c78 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Config/CustomOidcUserConfiguration.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Config/CustomOidcUserConfiguration.java @@ -1,5 +1,7 @@ package com.hithomelabs.CFTunnels.Config; +import com.hithomelabs.CFTunnels.Entity.User; +import com.hithomelabs.CFTunnels.Repositories.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.GrantedAuthority; @@ -18,6 +20,9 @@ public class CustomOidcUserConfiguration extends OidcUserService { @Autowired AuthoritiesToGroupMapping authoritiesToGroupMapping; + @Autowired + UserRepository userRepository; + @Override public OidcUser loadUser(OidcUserRequest userRequest) { // * * Delegate to the default implementation for loading user and claims @@ -34,6 +39,16 @@ public class CustomOidcUserConfiguration extends OidcUserService { ); } + String email = oidcUser.getEmail(); + String name = oidcUser.getNickName(); + + userRepository.findByEmail(email).orElseGet(() -> { + User user = new User(); + user.setEmail(email); + user.setName(name); + return userRepository.save(user); + }); + // * * Return a new DefaultOidcUser with merged authorities return new DefaultOidcUser( mappedAuthorities, diff --git a/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java b/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java index 3362a83..8956177 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java @@ -4,24 +4,26 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.hithomelabs.CFTunnels.Config.AuthoritiesToGroupMapping; import com.hithomelabs.CFTunnels.Config.CloudflareConfig; import com.hithomelabs.CFTunnels.Config.RestTemplateConfig; +import com.hithomelabs.CFTunnels.Entity.User; import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader; import com.hithomelabs.CFTunnels.Models.Config; import com.hithomelabs.CFTunnels.Models.Ingress; import com.hithomelabs.CFTunnels.Models.TunnelResponse; +import com.hithomelabs.CFTunnels.Repositories.UserRepository; import com.hithomelabs.CFTunnels.Services.CloudflareAPIService; +import com.hithomelabs.CFTunnels.Services.MappingRequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.http.*; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @RestController @RequestMapping("/cloudflare") @@ -44,6 +46,12 @@ public class TunnelController implements ErrorController { @Autowired CloudflareAPIService cloudflareAPIService; + @Autowired + UserRepository userRepository; + + @Autowired + MappingRequestService mappingRequestService; + @PreAuthorize("hasAnyRole('USER')") @GetMapping("/whoami") public Map whoAmI(@AuthenticationPrincipal OidcUser oidcUser) { @@ -132,4 +140,14 @@ public class TunnelController implements ErrorController { return ResponseEntity.ok(jsonResponse); } + + @PreAuthorize("hasAnyRole('DEVELOPER')") + @PutMapping("/tunnel/{tunnelId}/request") + public ResponseEntity createTunnelMappingRequest(@PathVariable UUID tunnelId, @AuthenticationPrincipal OidcUser oidcUser, @RequestBody Ingress ingess){ + String email = oidcUser.getEmail(); + User user = userRepository.findByEmail(email).orElseThrow(() -> new RuntimeException("User Not Found: " + email)); + mappingRequestService.createMappingRequest(tunnelId, ingess, user); + return ResponseEntity.ok(ingess); + } + } diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/Mapping.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/Mapping.java new file mode 100644 index 0000000..0081205 --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/Mapping.java @@ -0,0 +1,34 @@ +package com.hithomelabs.CFTunnels.Entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +import java.util.UUID; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "mappings") +public class Mapping { + + @Id + @GeneratedValue + @Column(columnDefinition = "uuid", nullable = false, unique = true) + private UUID id; + + @Column(nullable = false) + private int port; + + @Column(length = 50, nullable = false) + private String subdomain; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "tunnel_id", nullable = false) + private Tunnel tunnel; +} diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/Request.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/Request.java new file mode 100644 index 0000000..8d73de8 --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/Request.java @@ -0,0 +1,45 @@ +package com.hithomelabs.CFTunnels.Entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "requests") +public class Request { + + @Id + @GeneratedValue + @Column(columnDefinition = "uuid", unique = true, nullable = false) + private UUID id; + + @OneToOne + @JoinColumn(name = "mapping_id", unique = true, nullable = false) + private Mapping mapping; + + @ManyToOne + @JoinColumn(name = "created_by", nullable = false) + private User createdBy; + + @ManyToOne + @JoinColumn(name = "accepted_by") + private User acceptedBy; + + public enum RequestStatus { + PENDING, + APPROVED, + REJECTED + } + + @Enumerated(EnumType.STRING) + @Column(length = 10, nullable = false) + private RequestStatus status; +} diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/Tunnel.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/Tunnel.java new file mode 100644 index 0000000..9c56663 --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/Tunnel.java @@ -0,0 +1,29 @@ +package com.hithomelabs.CFTunnels.Entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Table(name="tunnels") +public class Tunnel { + + @Id + @GeneratedValue + @Column(columnDefinition = "uuid", insertable = false, updatable = false, nullable = false) + private UUID id; + + @Column(length = 10, unique = true, nullable = false) + private String environment; + + @Column(name = "cf_tunnel_id", columnDefinition = "uuid", unique = true, nullable = false) + private UUID cfTunnelId; +} diff --git a/src/main/java/com/hithomelabs/CFTunnels/Entity/User.java b/src/main/java/com/hithomelabs/CFTunnels/Entity/User.java new file mode 100644 index 0000000..5783357 --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Entity/User.java @@ -0,0 +1,30 @@ +package com.hithomelabs.CFTunnels.Entity; +import jakarta.persistence.*; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.UUID; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "users") +public class User { + @Id + @GeneratedValue + @Column(columnDefinition = "uuid", insertable = false, updatable = false, nullable = false) + private UUID id; + + @Column(length = 50, nullable = false) + @Size(max = 50) + private String name; + + @Column(length = 50, nullable = false) + @Size(max = 50) + private String email; +} diff --git a/src/main/java/com/hithomelabs/CFTunnels/Models/Ingress.java b/src/main/java/com/hithomelabs/CFTunnels/Models/Ingress.java index 7c6c7be..11bfc88 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Models/Ingress.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Models/Ingress.java @@ -1,5 +1,6 @@ package com.hithomelabs.CFTunnels.Models; +import java.net.URI; import java.util.List; import java.util.Map; @@ -46,4 +47,13 @@ public class Ingress { public void setOriginRequest(Map originRequest) { this.originRequest = originRequest; } + + public int getPort(){ + URI url = URI.create(this.service); + return url.getPort(); + } + + public String getSubdomain(){ + return this.hostname.split("//")[0]; + } } diff --git a/src/main/java/com/hithomelabs/CFTunnels/Repositories/MappingRepository.java b/src/main/java/com/hithomelabs/CFTunnels/Repositories/MappingRepository.java new file mode 100644 index 0000000..73b469c --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Repositories/MappingRepository.java @@ -0,0 +1,11 @@ +package com.hithomelabs.CFTunnels.Repositories; + +import com.hithomelabs.CFTunnels.Entity.Mapping; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface MappingRepository extends JpaRepository { +} diff --git a/src/main/java/com/hithomelabs/CFTunnels/Repositories/RequestRepository.java b/src/main/java/com/hithomelabs/CFTunnels/Repositories/RequestRepository.java new file mode 100644 index 0000000..fe79625 --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Repositories/RequestRepository.java @@ -0,0 +1,11 @@ +package com.hithomelabs.CFTunnels.Repositories; + +import com.hithomelabs.CFTunnels.Entity.Request; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface RequestRepository extends JpaRepository { +} diff --git a/src/main/java/com/hithomelabs/CFTunnels/Repositories/TunnelRepository.java b/src/main/java/com/hithomelabs/CFTunnels/Repositories/TunnelRepository.java new file mode 100644 index 0000000..178f44e --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Repositories/TunnelRepository.java @@ -0,0 +1,13 @@ +package com.hithomelabs.CFTunnels.Repositories; + +import com.hithomelabs.CFTunnels.Entity.Tunnel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface TunnelRepository extends JpaRepository { + Optional findByCfTunnelId(UUID cfTunnelId); +} diff --git a/src/main/java/com/hithomelabs/CFTunnels/Repositories/UserRepository.java b/src/main/java/com/hithomelabs/CFTunnels/Repositories/UserRepository.java new file mode 100644 index 0000000..a1038a7 --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Repositories/UserRepository.java @@ -0,0 +1,14 @@ +package com.hithomelabs.CFTunnels.Repositories; + +import com.hithomelabs.CFTunnels.Entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByEmail(String email); +} diff --git a/src/main/java/com/hithomelabs/CFTunnels/Services/MappingRequestService.java b/src/main/java/com/hithomelabs/CFTunnels/Services/MappingRequestService.java new file mode 100644 index 0000000..65893c1 --- /dev/null +++ b/src/main/java/com/hithomelabs/CFTunnels/Services/MappingRequestService.java @@ -0,0 +1,53 @@ +package com.hithomelabs.CFTunnels.Services; + +import com.hithomelabs.CFTunnels.Entity.Mapping; +import com.hithomelabs.CFTunnels.Entity.Request; +import com.hithomelabs.CFTunnels.Entity.Tunnel; +import com.hithomelabs.CFTunnels.Entity.User; +import com.hithomelabs.CFTunnels.Models.Ingress; +import com.hithomelabs.CFTunnels.Repositories.MappingRepository; +import com.hithomelabs.CFTunnels.Repositories.RequestRepository; +import com.hithomelabs.CFTunnels.Repositories.TunnelRepository; +import com.hithomelabs.CFTunnels.Repositories.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Service +public class MappingRequestService { + + @Autowired + UserRepository userRepository; + + @Autowired + MappingRepository mappingRepository; + + @Autowired + RequestRepository requestRepository; + + @Autowired + TunnelRepository tunnelRepository; + + public Mapping createMapping(UUID tunnelId, Ingress ingress){ + Tunnel tunnel = tunnelRepository.findByCfTunnelId(tunnelId).orElseThrow(() -> new RuntimeException("Tunnel not found")); + Mapping mapping = new Mapping(); + mapping.setTunnel(tunnel); + mapping.setPort(ingress.getPort()); + mapping.setSubdomain(ingress.getSubdomain()); + return mappingRepository.save(mapping); + } + + public Request createRequest(Mapping mapping, User user){ + Request request = new Request(); + request.setMapping(mapping); + request.setCreatedBy(user); + request.setStatus(Request.RequestStatus.PENDING); + return requestRepository.save(request); + } + + public Request createMappingRequest(UUID tunnelId, Ingress ingress, User user){ + Mapping mapping = createMapping(tunnelId, ingress); + return createRequest(mapping, user); + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 1fa876d..4ff5844 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,37 +1,29 @@ --- schema.sql - --- Roles table -CREATE TABLE IF NOT EXISTS roles ( - role_id SERIAL PRIMARY KEY, - role_name VARCHAR(50) UNIQUE NOT NULL -); - --- Users table -CREATE TABLE IF NOT EXISTS users ( - user_id SERIAL PRIMARY KEY, - user_name VARCHAR(100) NOT NULL, - password VARCHAR(255) NOT NULL -); - --- User-Role Mapping table (many-to-many relationship) -CREATE TABLE IF NOT EXISTS user_role_mapping ( - mapping_id SERIAL PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, - role_id INTEGER NOT NULL REFERENCES roles(role_id) ON DELETE CASCADE -); - --- Tunnels table -CREATE TABLE IF NOT EXISTS tunnels ( - tunnel_id SERIAL PRIMARY KEY, - tunnel_name VARCHAR(100) NOT NULL, - tunnel_type VARCHAR(50) NOT NULL -); - --- Mapping Requests table -CREATE TABLE IF NOT EXISTS mapping_requests ( - request_id SERIAL PRIMARY KEY, - request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - status VARCHAR(20) NOT NULL, - user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL, - tunnel_id INTEGER REFERENCES tunnels(tunnel_id) ON DELETE SET NULL -); +--CREATE EXTENSION IF NOT EXISTS "pgcrypto"; +-- +--CREATE TABLE IF NOT EXISTS tunnels ( +-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(), +-- environment VARCHAR(10) NOT NULL, +-- cf_tunnel_id UUID UNIQUE NOT NULL +--); +-- +--CREATE TABLE IF NOT EXISTS users ( +-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(), +-- name VARCHAR(50) NOT NULL +--); +-- +--CREATE TABLE IF NOT EXISTS mappings ( +-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(), +-- tunnel_id UUID NOT NULL REFERENCES tunnels(id) ON DELETE CASCADE, +-- port INT NOT NULL, +-- subdomain VARCHAR(50) NOT NULL +---- UNIQUE (tunnel_id, port), +---- UNIQUE (tunnel_id, subdomain) +--); +-- +--CREATE TABLE IF NOT EXISTS requests ( +-- id UUID PRIMARY KEY DEFAULT gen_random_uuid(), +-- mapping_id UUID NOT NULL REFERENCES mappings(id) ON DELETE CASCADE, +-- created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT, +-- accepted_by UUID REFERENCES users(id) ON DELETE SET NULL, +-- status VARCHAR(20) NOT NULL CHECK (status IN ('PENDING', 'APPROVED', 'REJECTED')) +--); \ No newline at end of file