Compare commits

..

No commits in common. "c8a25cf4382ba403f47fe3f820aec694340644db" and "063d66b987771e350feade6ad0bf78db6532baf1" have entirely different histories.

18 changed files with 31 additions and 328 deletions

View File

@ -14,7 +14,7 @@ java {
} }
test { test {
systemProperty 'spring.profiles.active', 'ci' systemProperty 'spring.profiles.active', 'test'
useJUnitPlatform { useJUnitPlatform {
excludeTags 'integration' excludeTags 'integration'
} }
@ -54,7 +54,6 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'org.postgresql:postgresql'
implementation 'org.hibernate.validator:hibernate-validator' implementation 'org.hibernate.validator:hibernate-validator'
runtimeOnly 'com.h2database:h2'
} }
tasks.named('test') { tasks.named('test') {

View File

@ -14,7 +14,6 @@ services:
- HOST_PORT=${HOST_PORT} - HOST_PORT=${HOST_PORT}
- POSTGRES_USER=${POSTGRES_USERNAME} - POSTGRES_USER=${POSTGRES_USERNAME}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- SWAGGER_OAUTH_CLIENT_ID=${SWAGGER_OAUTH_CLIENT_ID}
env_file: env_file:
- stack.env - stack.env
restart: unless-stopped restart: unless-stopped

View File

@ -4,13 +4,11 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.hithomelabs.CFTunnels.Config.AuthoritiesToGroupMapping; import com.hithomelabs.CFTunnels.Config.AuthoritiesToGroupMapping;
import com.hithomelabs.CFTunnels.Config.CloudflareConfig; import com.hithomelabs.CFTunnels.Config.CloudflareConfig;
import com.hithomelabs.CFTunnels.Config.RestTemplateConfig; import com.hithomelabs.CFTunnels.Config.RestTemplateConfig;
import com.hithomelabs.CFTunnels.Entity.Request;
import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader; import com.hithomelabs.CFTunnels.Headers.AuthKeyEmailHeader;
import com.hithomelabs.CFTunnels.Models.Config; import com.hithomelabs.CFTunnels.Models.Config;
import com.hithomelabs.CFTunnels.Models.Ingress; import com.hithomelabs.CFTunnels.Models.Ingress;
import com.hithomelabs.CFTunnels.Models.TunnelResponse; import com.hithomelabs.CFTunnels.Models.TunnelResponse;
import com.hithomelabs.CFTunnels.Services.CloudflareAPIService; import com.hithomelabs.CFTunnels.Services.CloudflareAPIService;
import com.hithomelabs.CFTunnels.Services.MappingRequestService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.*; import org.springframework.http.*;
@ -46,9 +44,6 @@ public class TunnelController implements ErrorController {
@Autowired @Autowired
CloudflareAPIService cloudflareAPIService; CloudflareAPIService cloudflareAPIService;
@Autowired
MappingRequestService mappingRequestService;
@PreAuthorize("hasAnyRole('USER')") @PreAuthorize("hasAnyRole('USER')")
@GetMapping("/whoami") @GetMapping("/whoami")
public Map<String,Object> whoAmI(@AuthenticationPrincipal OidcUser oidcUser) { public Map<String,Object> whoAmI(@AuthenticationPrincipal OidcUser oidcUser) {
@ -137,14 +132,4 @@ public class TunnelController implements ErrorController {
return ResponseEntity.ok(jsonResponse); return ResponseEntity.ok(jsonResponse);
} }
@PreAuthorize("hasAnyRole('DEVELOPER')")
@PutMapping("/tunnel/{tunnelId}/request")
public ResponseEntity<Request> createTunnelMappingRequest(@PathVariable String tunnelId, @AuthenticationPrincipal OidcUser oidcUser, @RequestBody Ingress ingess){
Request request = mappingRequestService.createMappingRequest(tunnelId, ingess, oidcUser);
if(request.getId() != null)
return ResponseEntity.status(HttpStatus.CREATED).body(request);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
} }

View File

@ -1,34 +0,0 @@
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;
}

View File

@ -1,45 +0,0 @@
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;
}

View File

@ -1,29 +0,0 @@
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;
}

View File

@ -1,30 +0,0 @@
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;
}

View File

@ -1,11 +0,0 @@
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<Mapping, UUID> {
}

View File

@ -1,11 +0,0 @@
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<Request, UUID> {
}

View File

@ -1,13 +0,0 @@
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<Tunnel, UUID> {
Optional<Tunnel> findByCfTunnelId(UUID cfTunnelId);
}

View File

@ -1,14 +0,0 @@
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<User, UUID> {
Optional<User> findByEmail(String email);
}

View File

@ -1,71 +0,0 @@
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.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Service;
import java.util.Map;
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 = createMappingFromTunnelIngress(tunnel, ingress);
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(String tunnelId, Ingress ingress, OidcUser oidcUser){
User user = userRepository.findByEmail(oidcUser.getEmail()).orElseGet(()-> mapUser(oidcUser));
Mapping mapping = createMapping(UUID.fromString(tunnelId), ingress);
return createRequest(mapping, user);
}
public User mapUser(OidcUser oidcUser){
String email = oidcUser.getEmail();
String name = oidcUser.getNickName();
User user = new User();
user.setEmail(email);
user.setName(name);
userRepository.save(user);
return user;
}
public Mapping createMappingFromTunnelIngress(Tunnel tunnel, Ingress ingress){
Mapping mapping = new Mapping();
mapping.setTunnel(tunnel);
mapping.setPort(Integer.parseInt(ingress.getService().split(":")[2]));
mapping.setSubdomain(ingress.getHostname().split("\\.")[0]);
return mapping;
}
}

View File

@ -1,7 +0,0 @@
api.baseUrl=https://testcf.hithomelabs.com
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=none

View File

@ -1,9 +1,3 @@
cloudflare.accountId=${CLOUDFLARE_ACCOUNT_ID} cloudflare.accountId=${CLOUDFLARE_ACCOUNT_ID}
cloudflare.apiKey=${CLOUDFLARE_API_KEY} cloudflare.apiKey=${CLOUDFLARE_API_KEY}
cloudflare.email=${CLOUDFLARE_EMAIL} cloudflare.email=${CLOUDFLARE_EMAIL}
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=none

View File

@ -1,7 +1 @@
api.baseUrl=https://testcf.hithomelabs.com api.baseUrl=https://testcf.hithomelabs.com
spring.datasource.url: jdbc:h2:mem:testdb
spring.datasource.driver-class-name: org.h2.Driver
spring.datasource.username: sa
spring.datasource.password:
spring.datasource.jpa.hibernate.ddl-auto: none

View File

@ -18,13 +18,6 @@ spring.security.oauth2.client.provider.cftunnels.user-info-uri=https://auth.hith
spring.security.oauth2.client.provider.cftunnels.jwk-set-uri=https://auth.hithomelabs.com/application/o/cftunnels/jwks/ spring.security.oauth2.client.provider.cftunnels.jwk-set-uri=https://auth.hithomelabs.com/application/o/cftunnels/jwks/
spring.security.oauth2.client.provider.cftunnels.issuer-uri=https://auth.hithomelabs.com/application/o/cftunnels/ spring.security.oauth2.client.provider.cftunnels.issuer-uri=https://auth.hithomelabs.com/application/o/cftunnels/
springdoc.swagger-ui.oauth.client-id=${SWAGGER_OAUTH_CLIENT_ID}
springdoc.swagger-ui.oauth.client-secret= # leave empty for public client
springdoc.swagger-ui.oauth.use-pkce=true
springdoc.swagger-ui.oauth.scopes=openid,profile,email
springdoc.swagger-ui.oauth.authorization-url=https://auth.hithomelabs.com/application/o/authorize/
springdoc.swagger-ui.oauth.token-url=https://auth.hithomelabs.com/application/o/token/
spring.datasource.url=jdbc:postgresql://192.168.0.100:5432/cftunnel spring.datasource.url=jdbc:postgresql://192.168.0.100:5432/cftunnel
spring.datasource.username=${POSTGRES_USERNAME} spring.datasource.username=${POSTGRES_USERNAME}
spring.datasource.password=${POSTGRES_PASSWORD} spring.datasource.password=${POSTGRES_PASSWORD}

View File

@ -1,29 +1,37 @@
CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- schema.sql
CREATE TABLE IF NOT EXISTS tunnels ( -- Roles table
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), CREATE TABLE IF NOT EXISTS roles (
environment VARCHAR(10) NOT NULL, role_id SERIAL PRIMARY KEY,
cf_tunnel_id UUID UNIQUE NOT NULL role_name VARCHAR(50) UNIQUE NOT NULL
); );
-- Users table
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL user_name VARCHAR(100) NOT NULL,
password VARCHAR(255) NOT NULL
); );
CREATE TABLE IF NOT EXISTS mappings ( -- User-Role Mapping table (many-to-many relationship)
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), CREATE TABLE IF NOT EXISTS user_role_mapping (
tunnel_id UUID NOT NULL REFERENCES tunnels(id) ON DELETE CASCADE, mapping_id SERIAL PRIMARY KEY,
port INT NOT NULL, user_id INTEGER NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
subdomain VARCHAR(50) NOT NULL role_id INTEGER NOT NULL REFERENCES roles(role_id) ON DELETE CASCADE
-- UNIQUE (tunnel_id, port),
-- UNIQUE (tunnel_id, subdomain)
); );
CREATE TABLE IF NOT EXISTS requests ( -- Tunnels table
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), CREATE TABLE IF NOT EXISTS tunnels (
mapping_id UUID NOT NULL REFERENCES mappings(id) ON DELETE CASCADE, tunnel_id SERIAL PRIMARY KEY,
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT, tunnel_name VARCHAR(100) NOT NULL,
accepted_by UUID REFERENCES users(id) ON DELETE SET NULL, tunnel_type VARCHAR(50) NOT NULL
status VARCHAR(20) NOT NULL CHECK (status IN ('PENDING', 'APPROVED', 'REJECTED')) );
);
-- 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
);

View File

@ -10,7 +10,6 @@ import com.hithomelabs.CFTunnels.Models.Config;
import com.hithomelabs.CFTunnels.Models.Groups; import com.hithomelabs.CFTunnels.Models.Groups;
import com.hithomelabs.CFTunnels.Models.TunnelResponse; import com.hithomelabs.CFTunnels.Models.TunnelResponse;
import com.hithomelabs.CFTunnels.Services.CloudflareAPIService; import com.hithomelabs.CFTunnels.Services.CloudflareAPIService;
import com.hithomelabs.CFTunnels.Services.MappingRequestService;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -67,9 +66,6 @@ class TunnelControllerTest {
@MockitoBean @MockitoBean
RestTemplateConfig restTemplateConfig; RestTemplateConfig restTemplateConfig;
@MockitoBean
MappingRequestService mappingRequestService;
private static final String tunnelResponseSmallIngressFile = "tunnelResponseSmallIngress.json"; private static final String tunnelResponseSmallIngressFile = "tunnelResponseSmallIngress.json";
private static final String tunnelResponseLargeIngressFile = "tunnelResponseLargeIngress.json"; private static final String tunnelResponseLargeIngressFile = "tunnelResponseLargeIngress.json";