Compare commits
No commits in common. "main" and "0.10.6" have entirely different histories.
@ -1,9 +1,7 @@
|
||||
name: Daily cloudflare API integration test
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '0 */12 * * *' # Every hour
|
||||
- cron: '0 */6 * * *' # Every hour
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@ -64,4 +64,23 @@ jobs:
|
||||
run: |
|
||||
docker push 192.168.0.100:8928/hithomelabs/cftunnels:test
|
||||
docker push 192.168.0.100:8928/hithomelabs/cftunnels:${{ needs.tag.outputs.new_version }}
|
||||
|
||||
sync_forks:
|
||||
name: Sync All Forks
|
||||
runs-on: ubuntu-latest
|
||||
needs: build_tag_push
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Sync all forks via Gitea API
|
||||
run: |
|
||||
echo "Fetching forks for Hithomelabs/CFTunnels..."
|
||||
response=$(curl -s -X GET "https://gitea.hithomelabs.com/api/v1/repos/Hithomelabs/CFTunnels/forks" -H "Authorization: token ${{secrets.TOKEN}}")
|
||||
filtered=$(echo "$response" | grep -o '"clone_url":"[^"]*"' | sed 's/"clone_url":"\([^"]*\)"/\1/' | grep -v "/Hithomelabs")
|
||||
echo "Detected forks:"
|
||||
echo "$filtered"
|
||||
readarray -t forks <<< "$filtered"
|
||||
for fork_url in "${forks[@]}"; do
|
||||
echo "🔄 Syncing fork: $fork_url"
|
||||
authed_url=$(echo "$fork_url" | sed "s#https://#https://${{secrets.TOKEN}}@#")
|
||||
git push "$authed_url" test &
|
||||
done
|
||||
|
||||
@ -14,7 +14,7 @@ java {
|
||||
}
|
||||
|
||||
test {
|
||||
systemProperty 'spring.profiles.active', 'ci'
|
||||
systemProperty 'spring.profiles.active', 'test'
|
||||
useJUnitPlatform {
|
||||
excludeTags 'integration'
|
||||
}
|
||||
@ -54,7 +54,6 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
implementation 'org.hibernate.validator:hibernate-validator'
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
|
||||
@ -14,7 +14,6 @@ services:
|
||||
- HOST_PORT=${HOST_PORT}
|
||||
- POSTGRES_USER=${POSTGRES_USERNAME}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
- SWAGGER_OAUTH_CLIENT_ID=${SWAGGER_OAUTH_CLIENT_ID}
|
||||
env_file:
|
||||
- stack.env
|
||||
restart: unless-stopped
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package com.hithomelabs.CFTunnels.Config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.security.*;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -18,12 +16,6 @@ public class OpenApiConfig {
|
||||
@Value("${api.baseUrl}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${springdoc.swagger-ui.oauth.authorization-url}")
|
||||
private String authorizationUri;
|
||||
|
||||
@Value("${springdoc.swagger-ui.oauth.token-url}")
|
||||
private String tokenUri;
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI(){
|
||||
Server httpsServer = new Server().url(baseUrl);
|
||||
@ -31,24 +23,6 @@ public class OpenApiConfig {
|
||||
ArrayList<Server> servers = new ArrayList<>();
|
||||
servers.add(httpsServer);
|
||||
openApi.setServers(servers);
|
||||
openApi.addSecurityItem(new SecurityRequirement().addList("oidcAuth"))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes("oidcAuth",
|
||||
new SecurityScheme()
|
||||
.type(SecurityScheme.Type.OAUTH2)
|
||||
.flows(new OAuthFlows()
|
||||
.authorizationCode(new OAuthFlow()
|
||||
.authorizationUrl(authorizationUri)
|
||||
.tokenUrl(tokenUri)
|
||||
.scopes(new Scopes()
|
||||
.addString("openid", "OpenID scope")
|
||||
.addString("profile", "OpenID profile")
|
||||
.addString("email", "OpenID email"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.addSecurityItem(new SecurityRequirement().addList("oidcAuth"));
|
||||
return openApi;
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +28,6 @@ public class SecuirtyConfig {
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
//.requestMatchers( "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html" ).permitAll()
|
||||
.anyRequest().authenticated()
|
||||
).csrf(csrf -> csrf.disable())
|
||||
.with(new OAuth2LoginConfigurer<>(),
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package com.hithomelabs.CFTunnels.Controllers;
|
||||
|
||||
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
public class HomeController implements ErrorController {
|
||||
|
||||
private static final String ERROR_PATH = "/error";
|
||||
|
||||
/**
|
||||
* Redirects the root (including any query params like ?continue=…)
|
||||
* straight into Swagger UI.
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public String rootRedirect() {
|
||||
return "redirect:/swagger-ui/index.html";
|
||||
}
|
||||
|
||||
/**
|
||||
* Catches any errors (404s, unhandled paths) and punts them
|
||||
* into the same Swagger UI page.
|
||||
*/
|
||||
@RequestMapping(ERROR_PATH)
|
||||
public String onError() {
|
||||
return "redirect:/swagger-ui/index.html";
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,15 +4,11 @@ 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.Request;
|
||||
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.Services.CloudflareAPIService;
|
||||
import com.hithomelabs.CFTunnels.Services.MappingRequestService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.http.*;
|
||||
@ -48,9 +44,6 @@ public class TunnelController implements ErrorController {
|
||||
@Autowired
|
||||
CloudflareAPIService cloudflareAPIService;
|
||||
|
||||
@Autowired
|
||||
MappingRequestService mappingRequestService;
|
||||
|
||||
@PreAuthorize("hasAnyRole('USER')")
|
||||
@GetMapping("/whoami")
|
||||
public Map<String,Object> whoAmI(@AuthenticationPrincipal OidcUser oidcUser) {
|
||||
@ -66,7 +59,6 @@ public class TunnelController implements ErrorController {
|
||||
|
||||
@PreAuthorize("hasAnyRole('USER')")
|
||||
@GetMapping("/tunnels")
|
||||
@Operation( security = { @SecurityRequirement(name = "oidcAuth") } )
|
||||
public ResponseEntity<Map<String,Object>> getTunnels(){
|
||||
|
||||
ResponseEntity<Map> responseEntity = cloudflareAPIService.getCloudflareTunnels();
|
||||
@ -78,7 +70,7 @@ public class TunnelController implements ErrorController {
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyRole('DEVELOPER')")
|
||||
@GetMapping("/tunnel/{tunnelId}/mappings")
|
||||
@GetMapping("/tunnel/{tunnelId}")
|
||||
public ResponseEntity<Map<String,Object>> getTunnelConfigurations(@PathVariable String tunnelId) {
|
||||
|
||||
ResponseEntity<Map> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplate, Map.class);
|
||||
@ -91,7 +83,7 @@ public class TunnelController implements ErrorController {
|
||||
|
||||
// 50df9101-f625-4618-b7c5-100338a57124
|
||||
@PreAuthorize("hasAnyRole('ADMIN')")
|
||||
@PostMapping("/tunnel/{tunnelId}/mappings")
|
||||
@PutMapping("/tunnel/{tunnelId}/add")
|
||||
public ResponseEntity<Map<String, Object>> addTunnelconfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException {
|
||||
|
||||
ResponseEntity<TunnelResponse> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class);
|
||||
@ -113,7 +105,7 @@ public class TunnelController implements ErrorController {
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyRole('DEVELOPER')")
|
||||
@DeleteMapping("/tunnel/{tunnelId}/mappings")
|
||||
@PutMapping("/tunnel/{tunnelId}/delete")
|
||||
public ResponseEntity<Map<String, Object>> deleteTunnelConfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException {
|
||||
|
||||
ResponseEntity<TunnelResponse> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class);
|
||||
@ -140,14 +132,4 @@ public class TunnelController implements ErrorController {
|
||||
|
||||
return ResponseEntity.ok(jsonResponse);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAnyRole('DEVELOPER')")
|
||||
@PostMapping("/tunnel/{tunnelId}/requests")
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -1,17 +1,8 @@
|
||||
package com.hithomelabs.CFTunnels.Models;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class Ingress {
|
||||
|
||||
private String service;
|
||||
@ -19,8 +10,40 @@ public class Ingress {
|
||||
private Map<String, Object> originRequest;
|
||||
private String path;
|
||||
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public static boolean deleteByHostName(List<Ingress> ingressList, String toBeDeleted){
|
||||
return ingressList.removeIf(ingress -> ingress.getHostname() != null && ingress.getHostname().equals(toBeDeleted));
|
||||
}
|
||||
|
||||
public String getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
public void setService(String service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = hostname;
|
||||
}
|
||||
|
||||
public Map<String, Object> getOriginRequest() {
|
||||
return originRequest;
|
||||
}
|
||||
|
||||
public void setOriginRequest(Map<String, Object> originRequest) {
|
||||
this.originRequest = originRequest;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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> {
|
||||
}
|
||||
@ -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> {
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -1,9 +1,3 @@
|
||||
cloudflare.accountId=${CLOUDFLARE_ACCOUNT_ID}
|
||||
cloudflare.apiKey=${CLOUDFLARE_API_KEY}
|
||||
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
|
||||
@ -1,12 +1 @@
|
||||
api.baseUrl=https://cftunnels.hithomelabs.com
|
||||
|
||||
# Production Database Configuration
|
||||
spring.datasource.url=jdbc:postgresql://postgres:5432/cftunnel
|
||||
spring.datasource.username=${POSTGRES_USERNAME}
|
||||
spring.datasource.password=${POSTGRES_PASSWORD}
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# JPA Configuration
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=false
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||
@ -1,12 +1 @@
|
||||
api.baseUrl=https://testcf.hithomelabs.com
|
||||
|
||||
# Test Database Configuration - Same as Production
|
||||
spring.datasource.url=jdbc:postgresql://postgres:5432/cftunnel
|
||||
spring.datasource.username=${POSTGRES_USERNAME}
|
||||
spring.datasource.password=${POSTGRES_PASSWORD}
|
||||
spring.datasource.driver-class-name=org.postgresql.Driver
|
||||
|
||||
# JPA Configuration
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
@ -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.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.username=${POSTGRES_USERNAME}
|
||||
spring.datasource.password=${POSTGRES_PASSWORD}
|
||||
|
||||
@ -1,29 +1,37 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
-- schema.sql
|
||||
|
||||
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
|
||||
-- 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 (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(50) NOT NULL
|
||||
user_id SERIAL PRIMARY KEY,
|
||||
user_name VARCHAR(100) NOT NULL,
|
||||
password VARCHAR(255) 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)
|
||||
-- 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
|
||||
);
|
||||
|
||||
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'))
|
||||
-- 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
|
||||
);
|
||||
@ -10,7 +10,6 @@ import com.hithomelabs.CFTunnels.Models.Config;
|
||||
import com.hithomelabs.CFTunnels.Models.Groups;
|
||||
import com.hithomelabs.CFTunnels.Models.TunnelResponse;
|
||||
import com.hithomelabs.CFTunnels.Services.CloudflareAPIService;
|
||||
import com.hithomelabs.CFTunnels.Services.MappingRequestService;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -36,7 +35,8 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
@ -66,9 +66,6 @@ class TunnelControllerTest {
|
||||
@MockitoBean
|
||||
RestTemplateConfig restTemplateConfig;
|
||||
|
||||
@MockitoBean
|
||||
MappingRequestService mappingRequestService;
|
||||
|
||||
private static final String tunnelResponseSmallIngressFile = "tunnelResponseSmallIngress.json";
|
||||
|
||||
private static final String tunnelResponseLargeIngressFile = "tunnelResponseLargeIngress.json";
|
||||
@ -161,7 +158,7 @@ class TunnelControllerTest {
|
||||
|
||||
when(cloudflareAPIService.getCloudflareTunnelConfigurations(eq("sampleTunnelId"), any(RestTemplate.class), eq(Map.class))).thenReturn(mockResponse);
|
||||
|
||||
mockMvc.perform(get("/cloudflare/tunnel/{tunnelId}/mappings", "sampleTunnelId")
|
||||
mockMvc.perform(get("/cloudflare/tunnel/{tunnelId}", "sampleTunnelId")
|
||||
.with(oauth2Login().oauth2User(buildOidcUser("username", Groups.HOMELAB_DEVELOPER))))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
|
||||
@ -183,7 +180,7 @@ class TunnelControllerTest {
|
||||
ResponseEntity<TunnelResponse> expectedHttpTunnelResponse = new ResponseEntity<>(expectedTunnelConfig, HttpStatus.OK);
|
||||
when(cloudflareAPIService.putCloudflareTunnelConfigurations(eq("sampleTunnelId"), any(RestTemplate.class), eq(TunnelResponse.class), any(Config.class))).thenReturn(expectedHttpTunnelResponse);
|
||||
|
||||
mockMvc.perform(post("/cloudflare/tunnel/{tunnelId}/mappings", "sampleTunnelId")
|
||||
mockMvc.perform(put("/cloudflare/tunnel/{tunnelId}/add", "sampleTunnelId")
|
||||
.with(oauth2Login().oauth2User(buildOidcUser("admin", Groups.SYSTEM_ADMIN)))
|
||||
.with(csrf())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
@ -208,7 +205,7 @@ class TunnelControllerTest {
|
||||
ResponseEntity<TunnelResponse> expectedHttpTunnelResponse = new ResponseEntity<>(expectedTunnelConfig, HttpStatus.OK);
|
||||
when(cloudflareAPIService.putCloudflareTunnelConfigurations(eq("sampleTunnelId"), any(RestTemplate.class), eq(TunnelResponse.class), any(Config.class))).thenReturn(expectedHttpTunnelResponse);
|
||||
|
||||
mockMvc.perform(delete("/cloudflare/tunnel/{tunnelId}/mappings", "sampleTunnelId")
|
||||
mockMvc.perform(put("/cloudflare/tunnel/{tunnelId}/delete", "sampleTunnelId")
|
||||
.with(oauth2Login().oauth2User(buildOidcUser("admin", Groups.SYSTEM_ADMIN)))
|
||||
.with(csrf())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
package com.hithomelabs.CFTunnels.Integration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.hithomelabs.CFTunnels.Config.CloudflareConfig;
|
||||
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.Services.CloudflareAPIService;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("integration")
|
||||
@ -27,7 +27,7 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
public class CoudflareApiIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
RestTemplate restTemplate;
|
||||
TestRestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
AuthKeyEmailHeader authKeyEmailHeader;
|
||||
@ -35,73 +35,21 @@ public class CoudflareApiIntegrationTest {
|
||||
@Autowired
|
||||
CloudflareConfig cloudflareConfig;
|
||||
|
||||
@Autowired
|
||||
CloudflareAPIService cloudflareAPIService;
|
||||
|
||||
private static final String DEV_TUNNEL_ID = "50df9101-f625-4618-b7c5-100338a57124";
|
||||
private static ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Test
|
||||
@DisplayName("Calls cloudflare cfd tunnels API and checks that dev tunnel should be a part of the response")
|
||||
public void testGetTunnelsTest() {
|
||||
|
||||
ResponseEntity<Map> response = cloudflareAPIService.getCloudflareTunnels();
|
||||
public void getTunnelsTest(){
|
||||
|
||||
// * * Resource URL to hit get request at
|
||||
String url = "https://api.cloudflare.com/client/v4/accounts/" + cloudflareConfig.getAccountId() + "/cfd_tunnel";
|
||||
HttpEntity<String> httpEntity = new HttpEntity<>("", authKeyEmailHeader.getHttpHeaders());
|
||||
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
|
||||
List<Map<String, Object>> tunnelList = (List<Map<String, Object>>) response.getBody().get("result");
|
||||
boolean hasName = tunnelList.stream()
|
||||
.anyMatch(tunnel -> "devtunnel".equals(tunnel.get("name")));
|
||||
|
||||
assertTrue(hasName);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Calls cloudflare API to get mappings for devtunnel tunnel")
|
||||
public void testTunnelConfigurations() {
|
||||
|
||||
ResponseEntity<TunnelResponse> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(DEV_TUNNEL_ID, restTemplate, TunnelResponse.class);
|
||||
|
||||
// * * Check if status code is 200
|
||||
assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
|
||||
|
||||
// * * Checking if mapping for devdocker exists
|
||||
TunnelResponse tunnelResponse = responseEntity.getBody();
|
||||
boolean hasMatch = tunnelResponse.getResult().getConfig().getIngress().stream()
|
||||
.anyMatch(ingress -> "devdocker.hithomelabs.com".equals(ingress.getHostname()));
|
||||
assertTrue(hasMatch);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Inserts and deletes a mapping using Cloudflare API")
|
||||
public void testAddAndDeleteMapping() {
|
||||
|
||||
ResponseEntity<TunnelResponse> beforeMapping = cloudflareAPIService.getCloudflareTunnelConfigurations(DEV_TUNNEL_ID, restTemplate, TunnelResponse.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, beforeMapping.getStatusCode());
|
||||
|
||||
Ingress ingress = new Ingress();
|
||||
ingress.setHostname("random.hithomelabs.com");
|
||||
ingress.setService("http://192.168.0.100:3457");
|
||||
|
||||
Config beforeInsertConfig = beforeMapping.getBody().getResult().getConfig();
|
||||
List<Ingress> beforeInsert = beforeInsertConfig.getIngress();
|
||||
beforeInsert.add(beforeInsert.size() - 1, ingress);
|
||||
|
||||
ResponseEntity<TunnelResponse> afterInsert = cloudflareAPIService.putCloudflareTunnelConfigurations(DEV_TUNNEL_ID, restTemplate, TunnelResponse.class, beforeInsertConfig);
|
||||
|
||||
assertEquals(HttpStatus.OK, afterInsert.getStatusCode());
|
||||
Config afterInsertConfig = afterInsert.getBody().getResult().getConfig();
|
||||
List<Ingress> ingressList = afterInsertConfig.getIngress();
|
||||
|
||||
boolean hasIngress = ingressList.get(ingressList.size() - 2 ).getHostname().equals("random.hithomelabs.com");
|
||||
assertTrue(hasIngress);
|
||||
|
||||
Boolean deleteSuccess = Ingress.deleteByHostName(ingressList, ingress.getHostname());
|
||||
assertTrue(deleteSuccess);
|
||||
|
||||
ResponseEntity<TunnelResponse> afterDelete = cloudflareAPIService.putCloudflareTunnelConfigurations(DEV_TUNNEL_ID, restTemplate, TunnelResponse.class, afterInsertConfig);
|
||||
assertEquals(HttpStatus.OK, afterDelete.getStatusCode());
|
||||
assertFalse(afterDelete.getBody().getResult().getConfig().getIngress().stream().anyMatch(anyIngress -> "random.hithomelabs.com".equals(anyIngress.getHostname())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user