diff options
author | Brinda Santh <brindasanth@in.ibm.com> | 2019-09-06 14:37:04 -0400 |
---|---|---|
committer | Brinda Santh <brindasanth@in.ibm.com> | 2019-09-18 14:56:45 -0400 |
commit | 65279626aae2c414f023a85feb9e3fee41e7215c (patch) | |
tree | 9430645050f531ae1ec6d5a2a21807e59c91b109 /ms/blueprintsprocessor/application/src/main/kotlin | |
parent | 996d0b3caf7bf767747b8c369d2ccc579711d092 (diff) |
Refactor distribution module to application.
Change-Id: If6451215e1d1c3b1b5963bbe5c6cda1532f01ac5
Issue-ID: CCSDK-1697
Signed-off-by: Brinda Santh <brindasanth@in.ibm.com>
Diffstat (limited to 'ms/blueprintsprocessor/application/src/main/kotlin')
9 files changed, 513 insertions, 0 deletions
diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintGRPCServer.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintGRPCServer.kt new file mode 100644 index 000000000..160a1b1b4 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintGRPCServer.kt @@ -0,0 +1,58 @@ +/* + * Copyright © 2017-2018 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor + +import io.grpc.ServerBuilder +import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.BluePrintManagementGRPCHandler +import org.onap.ccsdk.cds.blueprintsprocessor.security.BasicAuthServerInterceptor +import org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.BluePrintProcessingGRPCHandler +import org.onap.ccsdk.cds.controllerblueprints.core.logger +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.context.ApplicationListener +import org.springframework.context.event.ContextRefreshedEvent +import org.springframework.stereotype.Component + +@ConditionalOnProperty(name = ["blueprintsprocessor.grpcEnable"], havingValue = "true") +@Component +open class BlueprintGRPCServer(private val bluePrintProcessingGRPCHandler: BluePrintProcessingGRPCHandler, + private val bluePrintManagementGRPCHandler: BluePrintManagementGRPCHandler, + private val authInterceptor: BasicAuthServerInterceptor) + : ApplicationListener<ContextRefreshedEvent> { + + private val log = logger(BlueprintGRPCServer::class) + + @Value("\${blueprintsprocessor.grpcPort}") + private val grpcPort: Int? = null + + override fun onApplicationEvent(event: ContextRefreshedEvent) { + try { + log.info("Starting Blueprint Processor GRPC Starting..") + val server = ServerBuilder + .forPort(grpcPort!!) + .intercept(authInterceptor) + .addService(bluePrintProcessingGRPCHandler) + .addService(bluePrintManagementGRPCHandler) + .build() + + server.start() + log.info("Blueprint Processor GRPC server started and ready to serve on port({})...", server.port) + } catch (e: Exception) { + log.error("*** Error ***", e) + } + } +} diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintHttpServer.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintHttpServer.kt new file mode 100644 index 000000000..4251fb5cb --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintHttpServer.kt @@ -0,0 +1,33 @@ +/* + * Copyright © 2017-2018 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor + +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory +import org.springframework.boot.web.server.WebServerFactoryCustomizer +import org.springframework.stereotype.Component + +@Component +open class BlueprintHttpServer : WebServerFactoryCustomizer<NettyReactiveWebServerFactory> { + + @Value("\${blueprintsprocessor.httpPort}") + private val httpPort: Int? = null + + override fun customize(serverFactory: NettyReactiveWebServerFactory) { + serverFactory.port = httpPort!! + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.kt new file mode 100644 index 000000000..3709a9785 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/BlueprintProcessorApplication.kt @@ -0,0 +1,41 @@ +/* + * Copyright © 2017-2018 AT&T Intellectual Property. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor + +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +import org.springframework.context.annotation.ComponentScan + +/** + * BlueprintProcessorApplication + * + * @author Brinda Santh + */ +@SpringBootApplication +@EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class]) +@ComponentScan(basePackages = ["org.onap.ccsdk.cds.blueprintsprocessor", "org.onap.ccsdk.cds.controllerblueprints"]) +open class BlueprintProcessorApplication + +fun main(args: Array<String>) { + // This is required for TemplateController.getStoredResult to accept a content-type value + // as a request parameter, e.g. &format=application%2Fxml is accepted + System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true") + + SpringApplication.run(BlueprintProcessorApplication::class.java, *args) +} diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/SwaggerConfig.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/SwaggerConfig.kt new file mode 100644 index 000000000..a8ee57d9d --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/SwaggerConfig.kt @@ -0,0 +1,60 @@ +/* + * Copyright © 2017-2018 AT&T Intellectual Property. + * Modifications Copyright © 2018 IBM. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor + +import io.swagger.annotations.Api +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import springfox.documentation.builders.PathSelectors +import springfox.documentation.builders.RequestHandlerSelectors +import springfox.documentation.service.ApiInfo +import springfox.documentation.service.Contact +import springfox.documentation.spi.DocumentationType +import springfox.documentation.spring.web.plugins.Docket + +/** + * SwaggerConfig + * + * @author Brinda Santh + */ +@Configuration +//@EnableSwagger2WebFlux +open class SwaggerConfig { + + @Bean + open fun api(): Docket { + return Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.withClassAnnotation(Api::class.java)) + .paths(PathSelectors.any()) + .build() + .apiInfo(apiInfo()) + } + + private fun apiInfo(): ApiInfo { + return ApiInfo( + "CDS Blueprints Processor APIs", + "Provide APIs to interact with CBA, their resolved resources and templates, and stored resource configurations.", + "0.7.0", + null, + Contact("CCSDK Team", "www.onap.org", "onap-discuss@lists.onap.org"), + "Apache 2.0", + "http://www.apache.org/licenses/LICENSE-2.0", + emptyList()) + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/WebConfig.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/WebConfig.kt new file mode 100644 index 000000000..5b12d8df7 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/WebConfig.kt @@ -0,0 +1,69 @@ +/* + * Copyright © 2017-2018 AT&T Intellectual Property. + * Modifications Copyright © 2018 IBM. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onap.ccsdk.cds.blueprintsprocessor + +import org.onap.ccsdk.cds.blueprintsprocessor.security.AuthenticationManager +import org.onap.ccsdk.cds.blueprintsprocessor.security.SecurityContextRepository +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.web.reactive.config.CorsRegistry +import org.springframework.web.reactive.config.ResourceHandlerRegistry +import org.springframework.web.reactive.config.WebFluxConfigurer + +/** + * WebConfig + * + * @author Brinda Santh + */ +@Configuration +open class WebConfig(private val authenticationManager: AuthenticationManager, + private val securityContextRepository: SecurityContextRepository) : WebFluxConfigurer { + + override fun addResourceHandlers(registry: ResourceHandlerRegistry) { + + registry.addResourceHandler("/swagger-ui.html**") + .addResourceLocations("classpath:/META-INF/resources/") + + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/") + } + + override fun addCorsMappings(corsRegistry: CorsRegistry) { + corsRegistry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("*") + .allowedHeaders("*") + .maxAge(3600) + } + + @Bean + open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { + return http.csrf().disable() + .formLogin().disable() + .httpBasic().disable() + .authenticationManager(authenticationManager) + .securityContextRepository(securityContextRepository!!) + .authorizeExchange() + .pathMatchers(HttpMethod.OPTIONS).permitAll() + .anyExchange().authenticated() + .and().build() + } +} diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/AuthenticationManager.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/AuthenticationManager.kt new file mode 100644 index 000000000..933425bc3 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/AuthenticationManager.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +* limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.security + +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.authentication.ReactiveAuthenticationManager +import org.springframework.security.core.Authentication +import org.springframework.security.core.AuthenticationException +import reactor.core.publisher.Mono + +@Configuration +open class AuthenticationManager(private val authenticationProvider: AuthenticationProvider) + : ReactiveAuthenticationManager { + + override fun authenticate(authentication: Authentication): Mono<Authentication> { + try { + return Mono.just(authenticationProvider.authenticate(authentication)) + } catch (e: AuthenticationException) { + return Mono.error(e) + } + + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/BasicAuthServerInterceptor.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/BasicAuthServerInterceptor.kt new file mode 100644 index 000000000..f821462af --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/BasicAuthServerInterceptor.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.security + +import io.grpc.* +import org.onap.ccsdk.cds.controllerblueprints.core.logger +import org.springframework.security.authentication.BadCredentialsException +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.AuthenticationException +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Component +import java.nio.charset.StandardCharsets +import java.util.* + +@Component +class BasicAuthServerInterceptor(private val authenticationManager: AuthenticationManager) + : ServerInterceptor { + + private val log = logger(BasicAuthServerInterceptor::class) + + override fun <ReqT, RespT> interceptCall( + call: ServerCall<ReqT, RespT>, + headers: Metadata, + next: ServerCallHandler<ReqT, RespT>): ServerCall.Listener<ReqT> { + val authHeader = headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER)) + + if (authHeader.isNullOrEmpty()) { + throw Status.UNAUTHENTICATED.withDescription("Missing required authentication") + .asRuntimeException() + } + + try { + val tokens = decodeBasicAuth(authHeader) + val username = tokens[0] + + log.info("Basic Authentication Authorization header found for user: {}", username) + + val authRequest = UsernamePasswordAuthenticationToken(username, tokens[1]) + val authResult = authenticationManager!!.authenticate(authRequest).block() + + log.info("Authentication success: {}", authResult) + + SecurityContextHolder.getContext().authentication = authResult + + } catch (e: AuthenticationException) { + SecurityContextHolder.clearContext() + + log.info("Authentication request failed: {}", e.message) + + throw Status.UNAUTHENTICATED.withDescription(e.message).withCause(e).asRuntimeException() + } + + return next.startCall(call, headers) + } + + private fun decodeBasicAuth(authHeader: String): Array<String> { + val basicAuth: String + try { + basicAuth = String(Base64.getDecoder().decode(authHeader.substring(6).toByteArray(StandardCharsets.UTF_8)), + StandardCharsets.UTF_8) + } catch (e: IllegalArgumentException) { + throw BadCredentialsException("Failed to decode basic authentication token") + } catch (e: IndexOutOfBoundsException) { + throw BadCredentialsException("Failed to decode basic authentication token") + } + + val delim = basicAuth.indexOf(':') + if (delim == -1) { + throw BadCredentialsException("Failed to decode basic authentication token") + } + + return arrayOf(basicAuth.substring(0, delim), basicAuth.substring(delim + 1)) + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/SecurityConfiguration.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/SecurityConfiguration.kt new file mode 100644 index 000000000..70b0df2d1 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/SecurityConfiguration.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019 Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.security + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.authentication.AuthenticationProvider +import org.springframework.security.authentication.dao.DaoAuthenticationProvider +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.userdetails.User +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder +import org.springframework.security.crypto.password.PasswordEncoder +import org.springframework.security.provisioning.InMemoryUserDetailsManager + +@Configuration +open class SecurityConfiguration { + + @Value("\${security.user.name}") + lateinit var username: String + + @Value("\${security.user.password}") + lateinit var password: String + + @Bean + open fun inMemoryUserService(): UserDetailsService { + val user = User(username, password, + listOf(SimpleGrantedAuthority("USER"))) + return InMemoryUserDetailsManager(user) + } + + @Bean + open fun passwordEncoder(): PasswordEncoder { + return BCryptPasswordEncoder() + } + + @Bean + open fun inMemoryAuthenticationProvider(): AuthenticationProvider { + val provider = DaoAuthenticationProvider() + provider.setUserDetailsService(inMemoryUserService()) + return provider + } +}
\ No newline at end of file diff --git a/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/SecurityContextRepository.kt b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/SecurityContextRepository.kt new file mode 100644 index 000000000..f1c362f57 --- /dev/null +++ b/ms/blueprintsprocessor/application/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/security/SecurityContextRepository.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 Bell Canada. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onap.ccsdk.cds.blueprintsprocessor.security + +import org.springframework.http.HttpHeaders +import org.springframework.security.authentication.BadCredentialsException +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.context.SecurityContext +import org.springframework.security.core.context.SecurityContextImpl +import org.springframework.security.web.server.context.ServerSecurityContextRepository +import org.springframework.stereotype.Component +import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.Mono +import java.nio.charset.StandardCharsets +import java.util.* + +@Component +class SecurityContextRepository(private val authenticationManager: AuthenticationManager) + : ServerSecurityContextRepository { + + override fun save(swe: ServerWebExchange, sc: SecurityContext): Mono<Void> { + throw UnsupportedOperationException("Not supported.") + } + + override fun load(swe: ServerWebExchange): Mono<SecurityContext> { + val request = swe.request + val authHeader = request.headers.getFirst(HttpHeaders.AUTHORIZATION) + if (authHeader != null && authHeader.startsWith("Basic")) { + val tokens = decodeBasicAuth(authHeader) + val username = tokens[0] + val password = tokens[1] + val auth = UsernamePasswordAuthenticationToken(username, password) + return this.authenticationManager!!.authenticate(auth) + .map { SecurityContextImpl(it) } + } else { + return Mono.empty() + } + } + + private fun decodeBasicAuth(authHeader: String): Array<String> { + val basicAuth: String + try { + basicAuth = String(Base64.getDecoder().decode(authHeader.substring(6).toByteArray(StandardCharsets.UTF_8)), + StandardCharsets.UTF_8) + } catch (e: IllegalArgumentException) { + throw BadCredentialsException("Failed to decode basic authentication token") + } catch (e: IndexOutOfBoundsException) { + throw BadCredentialsException("Failed to decode basic authentication token") + } + + val delim = basicAuth.indexOf(':') + if (delim == -1) { + throw BadCredentialsException("Failed to decode basic authentication token") + } + + return arrayOf(basicAuth.substring(0, delim), basicAuth.substring(delim + 1)) + } +}
\ No newline at end of file |