diff --git a/.github/workflows/open-api-ci-cd.yml b/.github/workflows/open-api-ci-cd.yml index 5824328..70c3bda 100644 --- a/.github/workflows/open-api-ci-cd.yml +++ b/.github/workflows/open-api-ci-cd.yml @@ -209,8 +209,9 @@ jobs: -e "OPEN_TOKEN_ADDRESS=${{ secrets.OPEN_TOKEN_ADDRESS_PROD }}" \ -e "EVENT_SUBSCRIPTION=true" \ -e "WIDGET_HOST=${{ secrets.WIDGET_HOST_PROD }}" \ - -e "OPEN_STATE_URL=${{ secrets.OPEN_STATE_URLPROD }}" \ + -e "OPEN_STATE_URL=${{ secrets.OPEN_STATE_URL_PROD }}" \ -e "STATE_API_URL=${{ secrets.STATE_API_URL_PROD }}" \ + -e "OPEN_KEY_URL=${{ secrets.OPEN_KEY_URL_PROD }}" \ ${{ env.DEPLOY_IMAGE_NAME }}:${{ env.DEPLOY_IMAGE_TAG }} " diff --git a/build.gradle b/build.gradle index 3f703aa..53e9aba 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,8 @@ dependencies { // Utils implementation('commons-io:commons-io:2.8.0') implementation 'org.apache.commons:commons-lang3:3.6' + implementation group: 'commons-net', name: 'commons-net', version: '3.6' + // Test testImplementation('org.springframework.boot:spring-boot-starter-test') diff --git a/frontend/src/components/GatewayApplicationRemove.js b/frontend/src/components/GatewayApplicationRemove.js index 88f4c86..34d83d8 100644 --- a/frontend/src/components/GatewayApplicationRemove.js +++ b/frontend/src/components/GatewayApplicationRemove.js @@ -3,9 +3,9 @@ import {t} from "../utils/messageTexts"; import React from "react"; export const GatewayApplicationRemove = ({ onSubmit }) => ( - +
- {t('sure to delete Gateway')} + {t('sure to delete Application')}
-); \ No newline at end of file +); diff --git a/src/main/kotlin/io/openfuture/api/config/SecurityConfig.kt b/src/main/kotlin/io/openfuture/api/config/SecurityConfig.kt index 16c163d..29cc843 100644 --- a/src/main/kotlin/io/openfuture/api/config/SecurityConfig.kt +++ b/src/main/kotlin/io/openfuture/api/config/SecurityConfig.kt @@ -41,12 +41,13 @@ class SecurityConfig( .antMatchers("/static/**").permitAll() .antMatchers("**.js").permitAll() .antMatchers("/widget/**").permitAll() + .antMatchers("/**").access("hasIpAddress('127.0.0.1') or hasIpAddress('0:0:0:0:0:0:0:1') or hasIpAddress('${properties.cidr}')") .anyRequest().authenticated() .and() .addFilterAfter(AuthorizationFilter(properties, keyService), OAuth2LoginAuthenticationFilter::class.java) - .addFilterAfter(ApiAuthorizationFilter(mapper), AuthorizationFilter::class.java) + .addFilterAfter(ApiAuthorizationFilter(mapper,properties), AuthorizationFilter::class.java) .addFilterAfter(PublicApiAuthorizationFilter(applicationService, mapper, properties), AuthorizationFilter::class.java) .sessionManagement().sessionCreationPolicy(STATELESS) diff --git a/src/main/kotlin/io/openfuture/api/config/filter/ApiAuthorizationFilter.kt b/src/main/kotlin/io/openfuture/api/config/filter/ApiAuthorizationFilter.kt index e7838c3..3ac844b 100644 --- a/src/main/kotlin/io/openfuture/api/config/filter/ApiAuthorizationFilter.kt +++ b/src/main/kotlin/io/openfuture/api/config/filter/ApiAuthorizationFilter.kt @@ -1,14 +1,22 @@ package io.openfuture.api.config.filter import com.fasterxml.jackson.databind.ObjectMapper +import io.openfuture.api.config.propety.AuthorizationProperties import io.openfuture.api.domain.exception.ExceptionResponse import org.springframework.http.HttpStatus.UNAUTHORIZED import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.web.util.matcher.IpAddressMatcher import javax.servlet.* import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse -class ApiAuthorizationFilter(private val mapper: ObjectMapper): Filter { +class ApiAuthorizationFilter( + private val mapper: ObjectMapper, + private val properties: AuthorizationProperties +): Filter { + + private val ipV4LoopBack = "127.0.0.1" + private val ipV6LoopBack = "0:0:0:0:0:0:0:1" override fun init(filterConfig: FilterConfig?) { // Do nothing @@ -18,7 +26,7 @@ class ApiAuthorizationFilter(private val mapper: ObjectMapper): Filter { request as HttpServletRequest response as HttpServletResponse - if (request.requestURI.startsWith("/api") && null == SecurityContextHolder.getContext().authentication) { + if (request.requestURI.startsWith("/api") && null == SecurityContextHolder.getContext().authentication && !isAllowed(request)) { val exceptionResponse = ExceptionResponse(UNAUTHORIZED.value(), "Open token is invalid or disabled") response.status = exceptionResponse.status response.writer.write(mapper.writeValueAsString(exceptionResponse)) @@ -32,4 +40,22 @@ class ApiAuthorizationFilter(private val mapper: ObjectMapper): Filter { // Do nothing } + fun isAllowed(request: HttpServletRequest): Boolean { + + val ip = request.remoteAddr + if (properties.allowLocalHost && (ipV4LoopBack == ip || ipV6LoopBack == ip)) { + return true + } + + if (properties.cidr != null) { + val matcher = IpAddressMatcher(properties.cidr) + + if (matcher.matches(request.getHeader("X-Forwarded-For"))) { + return true + } + } + + return false + } + } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/api/config/filter/IpAddressFilter.kt b/src/main/kotlin/io/openfuture/api/config/filter/IpAddressFilter.kt new file mode 100644 index 0000000..0dc22e0 --- /dev/null +++ b/src/main/kotlin/io/openfuture/api/config/filter/IpAddressFilter.kt @@ -0,0 +1,72 @@ +package io.openfuture.api.config.filter + +import io.openfuture.api.config.propety.AuthorizationProperties +import io.openfuture.api.util.getIpRange +import org.springframework.security.web.util.matcher.IpAddressMatcher +import java.io.IOException +import javax.servlet.* +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + + +class IpAddressFilter( + private val properties: AuthorizationProperties +) : Filter { + + private val IPV4_LOOPBACK = "127.0.0.1" + private val IPV6_LOOPBACK = "0:0:0:0:0:0:0:1" + private var ipList = arrayListOf() + var allowLocalhost = true + + override fun init(filterConfig: FilterConfig?) { + ipList = getIpRange(properties.cidr!!) + ipList.stream().map { ip -> print(ip) } + } + + override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { + request as HttpServletRequest + response as HttpServletResponse + + println("REMOTE ADDRESS ${request.getHeader("X-Forwarded-For")}") + + + if (!isAllowed(request)) { + println("DENIED") + deny(response) + return + } + chain.doFilter(request, response) + } + + @Throws(IOException::class) + fun deny(res: HttpServletResponse) { + res.sendError(HttpServletResponse.SC_NOT_FOUND) + } + + override fun destroy() { + + } + + fun isAllowed(request: HttpServletRequest): Boolean { + + val ip = request.remoteAddr + if (allowLocalhost && (IPV4_LOOPBACK == ip || IPV6_LOOPBACK == ip)) { + return true + } + /*var uri = request.getAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE) as String + if (!StringUtils.isEmpty(uri)) { + uri = request.requestURI + if (request.contextPath != "/" && uri.startsWith(request.contextPath)) { + uri = uri.substring(request.contextPath.length) + } + }*/ + + val matcher = IpAddressMatcher("192.168.1.0/24") + + if (!matcher.matches(request.getHeader("X-Forwarded-For"))) { + return true + } + + return false + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/api/config/filter/PublicApiAuthorizationFilter.kt b/src/main/kotlin/io/openfuture/api/config/filter/PublicApiAuthorizationFilter.kt index db5eec8..c798ccd 100644 --- a/src/main/kotlin/io/openfuture/api/config/filter/PublicApiAuthorizationFilter.kt +++ b/src/main/kotlin/io/openfuture/api/config/filter/PublicApiAuthorizationFilter.kt @@ -6,10 +6,7 @@ import org.springframework.http.HttpStatus.UNAUTHORIZED import io.openfuture.api.domain.exception.ExceptionResponse import io.openfuture.api.domain.key.WalletApiCreateRequest import io.openfuture.api.service.ApplicationService -import io.openfuture.api.util.CustomHttpRequestWrapper -import io.openfuture.api.util.KeyGeneratorUtils -import io.openfuture.api.util.currentEpochs -import io.openfuture.api.util.differenceEpochs +import io.openfuture.api.util.* import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.context.SecurityContextHolder @@ -36,7 +33,7 @@ class PublicApiAuthorizationFilter( val accessKey = request.getHeader("X-API-KEY") val signature = request.getHeader("X-API-SIGNATURE") - val expirePeriod = 10L; + val expirePeriod = properties.expireApi!! val requestWrapper = CustomHttpRequestWrapper(request) val walletApiCreateRequest = mapper.readValue(requestWrapper.bodyInStringFormat, WalletApiCreateRequest::class.java) @@ -59,7 +56,7 @@ class PublicApiAuthorizationFilter( val token = UsernamePasswordAuthenticationToken(application.user, null, listOf(SimpleGrantedAuthority("ROLE_APPLICATION"))) SecurityContextHolder.getContext().authentication = token - chain.doFilter(requestWrapper, response); + chain.doFilter(requestWrapper, response) return } diff --git a/src/main/kotlin/io/openfuture/api/config/propety/AuthorizationProperties.kt b/src/main/kotlin/io/openfuture/api/config/propety/AuthorizationProperties.kt index 8374bc7..c5cf559 100644 --- a/src/main/kotlin/io/openfuture/api/config/propety/AuthorizationProperties.kt +++ b/src/main/kotlin/io/openfuture/api/config/propety/AuthorizationProperties.kt @@ -10,5 +10,7 @@ import javax.validation.constraints.NotEmpty @Component class AuthorizationProperties( @field:NotEmpty var cookieName: String? = null, - var expireApi: Long? = 10 + var expireApi: Long? = 10, + var cidr: String? = null, + var allowLocalHost: Boolean = false ) \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/api/controller/api/ApplicationApiController.kt b/src/main/kotlin/io/openfuture/api/controller/api/ApplicationApiController.kt index a42b04c..f18454f 100644 --- a/src/main/kotlin/io/openfuture/api/controller/api/ApplicationApiController.kt +++ b/src/main/kotlin/io/openfuture/api/controller/api/ApplicationApiController.kt @@ -37,7 +37,7 @@ class ApplicationApiController( } @GetMapping("/{id}") - fun get(@CurrentUser user: User, @PathVariable id: Long): ApplicationDto { + fun get(@PathVariable id: Long): ApplicationDto { val application = service.getById(id) return ApplicationDto(application) } diff --git a/src/main/kotlin/io/openfuture/api/service/DefaultApplicationWalletService.kt b/src/main/kotlin/io/openfuture/api/service/DefaultApplicationWalletService.kt index f2224b7..fbf3357 100644 --- a/src/main/kotlin/io/openfuture/api/service/DefaultApplicationWalletService.kt +++ b/src/main/kotlin/io/openfuture/api/service/DefaultApplicationWalletService.kt @@ -22,7 +22,7 @@ class DefaultApplicationWalletService( val keyWalletDto = keyApi.generateKey(CreateKeyRequest(request.applicationId, user.id.toString(), request.blockchainType)) // Save webhook on state - request.webHook.let { stateApi.createWallet(keyWalletDto.address, it, Blockchain.Ethereum) } + //request.webHook.let { stateApi.createWallet(keyWalletDto.address, it, Blockchain.Ethereum) } return keyWalletDto } @@ -35,6 +35,6 @@ class DefaultApplicationWalletService( // Delete from Open Key keyApi.deleteAllKeysByApplicationAddress(applicationId, address) // Delete from Open State - stateApi.deleteWallet(address, Blockchain.Ethereum) + //stateApi.deleteWallet(address, Blockchain.Ethereum) } } \ No newline at end of file diff --git a/src/main/kotlin/io/openfuture/api/util/NetworkUtils.kt b/src/main/kotlin/io/openfuture/api/util/NetworkUtils.kt new file mode 100644 index 0000000..e97a30e --- /dev/null +++ b/src/main/kotlin/io/openfuture/api/util/NetworkUtils.kt @@ -0,0 +1,9 @@ +package io.openfuture.api.util + +import org.apache.commons.net.util.SubnetUtils + +fun getIpRange(subnet: String): ArrayList { + val utils = SubnetUtils(subnet) + + return utils.info.allAddresses!!.toCollection(ArrayList()) +} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 0000000..90ae774 --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,27 @@ +# SECURITY +spring.security.oauth2.client.registration.google.clientId=${GOOGLE_CLIENT_ID} +spring.security.oauth2.client.registration.google.clientSecret=${GOOGLE_CLIENT_SECRET} + +# DATABASE +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://${POSTGRES_HOST}/${POSTGRES_DB} +spring.datasource.username=${POSTGRES_USER} +spring.datasource.password=${POSTGRES_PASSWORD} +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.database=postgresql +spring.flyway.out-of-order=true + +# WEB3 +web3j.client-address=${NETWORK_URL} +ethereum.private-key=${ETHEREUM_PRIVATE_KEY} +ethereum.open-token-address=${OPEN_TOKEN_ADDRESS} +ethereum.event-subscription=${EVENT_SUBSCRIPTION} + +# AUTH +auth.cookie-name=open_key + +# WIDGET +widget.host=${WIDGET_HOST} + +# STATE +state.base-url=${OPEN_STATE_URL} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1e78774..f2677ed 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,6 +20,8 @@ ethereum.event-subscription=${EVENT_SUBSCRIPTION} # AUTH auth.cookie-name=open_key auth.expire-api=10 +auth.cidr=${PUBLIC_IP_SUBNET} +auth.allow-local-host=false # WIDGET widget.host=${WIDGET_HOST}