From bd28818bdba4ede90fe80877bf6839004516000a Mon Sep 17 00:00:00 2001 From: "Muthuramalingam, Brinda Santh(bs2796)" Date: Sat, 1 Dec 2018 19:54:07 -0500 Subject: Enable Webflux Service. Change-Id: I99bacee9b63aa788bb368dec60981bf19ea759c4 Issue-ID: CCSDK-781 Signed-off-by: Muthuramalingam, Brinda Santh(bs2796) --- .../ApplicationExceptionHandler.java | 74 ------------------ .../apps/controllerblueprints/SwaggerConfig.java | 7 +- .../ccsdk/apps/controllerblueprints/WebConfig.java | 11 +++ .../filters/ApplicationLoggingFilter.java | 91 ++++++++++++---------- .../controllerblueprints/filters/CorsFilter.java | 64 --------------- .../ApplicationBasicAuthenticationEntryPoint.java | 43 ---------- .../ApplicationSecurityConfigurerAdapter.java | 74 ++++++++---------- 7 files changed, 98 insertions(+), 266 deletions(-) delete mode 100644 ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/ApplicationExceptionHandler.java delete mode 100644 ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/filters/CorsFilter.java delete mode 100644 ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/security/ApplicationBasicAuthenticationEntryPoint.java (limited to 'ms/controllerblueprints/application/src/main') diff --git a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/ApplicationExceptionHandler.java b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/ApplicationExceptionHandler.java deleted file mode 100644 index 78706d57..00000000 --- a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/ApplicationExceptionHandler.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.apps.controllerblueprints; - -import com.att.eelf.configuration.EELFLogger; -import com.att.eelf.configuration.EELFManager; -import org.onap.ccsdk.apps.controllerblueprints.core.BluePrintException; -import org.onap.ccsdk.apps.controllerblueprints.service.common.ErrorMessage; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.web.csrf.InvalidCsrfTokenException; -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.request.WebRequest; - -import javax.naming.AuthenticationException; -import java.nio.file.AccessDeniedException; - -@ControllerAdvice -@RestController -@SuppressWarnings("unused") -public class ApplicationExceptionHandler { - private static EELFLogger log = EELFManager.getInstance().getLogger(ApplicationExceptionHandler.class); - - @ExceptionHandler(Exception.class) - public final ResponseEntity handleAllExceptions(Exception ex, WebRequest request) { - log.error("Application Exception", ex); - ErrorMessage exceptionResponse = new ErrorMessage(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getLocalizedMessage()); - return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler({InvalidCsrfTokenException.class, AuthenticationException.class, BadCredentialsException.class, AccessDeniedException.class}) - @ResponseStatus(value = HttpStatus.UNAUTHORIZED) - public final ResponseEntity handleAuthenticationRequest(Exception ex, WebRequest request) { - log.error("Authentication Exception", ex); - ErrorMessage exceptionResponse = new ErrorMessage(ex.getMessage(), HttpStatus.UNAUTHORIZED.value(), ex.getLocalizedMessage()); - return new ResponseEntity<>(exceptionResponse, HttpStatus.UNAUTHORIZED); - } - - @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class, - HttpRequestMethodNotSupportedException.class}) - public final ResponseEntity handleBadRequest(Exception ex, WebRequest request) { - log.error("Bad Request Exception", ex); - ErrorMessage exceptionResponse = new ErrorMessage(ex.getMessage(), HttpStatus.BAD_REQUEST.value(), ex.getLocalizedMessage()); - return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(BluePrintException.class) - public final ResponseEntity handleBlueprintException(BluePrintException ex, WebRequest request) { - log.error("Application Blueprint Exception", ex); - ErrorMessage exceptionResponse = new ErrorMessage(ex.getMessage(), ex.getCode(), ex.getLocalizedMessage()); - return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR); - } -} diff --git a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/SwaggerConfig.java b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/SwaggerConfig.java index 8b96f04a..b9c0bd19 100644 --- a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/SwaggerConfig.java +++ b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/SwaggerConfig.java @@ -21,7 +21,6 @@ import org.jetbrains.annotations.NotNull; import org.onap.ccsdk.apps.controllerblueprints.core.BluePrintConstants; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMethod; import springfox.documentation.builders.PathSelectors; @@ -34,7 +33,6 @@ import springfox.documentation.service.Header; import springfox.documentation.service.ResponseMessage; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.Collections; import java.util.HashMap; @@ -46,8 +44,9 @@ import java.util.Map; * * @author Brinda Santh 8/13/2018 */ -@Configuration -@EnableSwagger2 +@Deprecated +//@Configuration +//@EnableSwagger2 @SuppressWarnings("unused") public class SwaggerConfig { @Value("${appVersion}") diff --git a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/WebConfig.java b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/WebConfig.java index 83f5f19e..45faa1b5 100644 --- a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/WebConfig.java +++ b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/WebConfig.java @@ -17,6 +17,7 @@ package org.onap.ccsdk.apps.controllerblueprints; import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.config.CorsRegistry; import org.springframework.web.reactive.config.ResourceHandlerRegistry; import org.springframework.web.reactive.config.WebFluxConfigurationSupport; @@ -35,5 +36,15 @@ public class WebConfig extends WebFluxConfigurationSupport { registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); + + } + + @Override + public void addCorsMappings(CorsRegistry corsRegistry) { + corsRegistry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("*") + .allowedHeaders("DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range") + .maxAge(3600); } } diff --git a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/filters/ApplicationLoggingFilter.java b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/filters/ApplicationLoggingFilter.java index 44761177..367ea7d9 100644 --- a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/filters/ApplicationLoggingFilter.java +++ b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/filters/ApplicationLoggingFilter.java @@ -20,66 +20,79 @@ package org.onap.ccsdk.apps.controllerblueprints.filters; import com.google.common.base.Preconditions; import org.apache.commons.lang3.StringUtils; import org.onap.ccsdk.apps.controllerblueprints.core.BluePrintConstants; -import org.onap.logging.ref.slf4j.ONAPLogAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Mono; -import javax.servlet.*; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; /** * ApplicationLoggingFilter * * @author Brinda Santh 8/14/2018 */ -@Component -@WebFilter(asyncSupported = true, urlPatterns = {"/*"}) -@Order(Ordered.HIGHEST_PRECEDENCE) +@Configuration @SuppressWarnings("unused") -public class ApplicationLoggingFilter implements Filter { +public class ApplicationLoggingFilter implements WebFilter { private static Logger log = LoggerFactory.getLogger(ApplicationLoggingFilter.class); @SuppressWarnings("unused") @Value("${appVersion}") private String appVersion; - public void doFilter(ServletRequest request, - ServletResponse response, - FilterChain chain) throws IOException, ServletException { - - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse res = (HttpServletResponse) response; - - ONAPLogAdapter onapLogAdapter = new ONAPLogAdapter(log); - onapLogAdapter.entering(req); - - String[] tokens = StringUtils.split(appVersion, '.'); - Preconditions.checkNotNull(tokens, "failed to split application versions"); - Preconditions.checkArgument(tokens.length == 3, "failed to tokenize application versions"); - res.addHeader(BluePrintConstants.RESPONSE_HEADER_TRANSACTION_ID, MDC.get("RequestID")); - res.addHeader(BluePrintConstants.RESPONSE_HEADER_MINOR_VERSION, tokens[1]); - res.addHeader(BluePrintConstants.RESPONSE_HEADER_PATCH_VERSION, tokens[2]); - res.addHeader(BluePrintConstants.RESPONSE_HEADER_LATEST_VERSION, appVersion); - chain.doFilter(request, response); - // Clean the MDC info - onapLogAdapter.exiting(); + @Override + public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) { + try { + + ServerHttpRequest request = serverWebExchange.getRequest(); + ServerHttpResponse response = serverWebExchange.getResponse(); + + String[] tokens = StringUtils.split(appVersion, '.'); + Preconditions.checkNotNull(tokens, "failed to split application versions"); + Preconditions.checkArgument(tokens.length == 3, "failed to tokenize application versions"); + HttpHeaders header = response.getHeaders(); + + String requestID = defaultToUUID(request.getHeaders().getFirst("X-ONAP-RequestID")); + String invocationID = defaultToUUID(request.getHeaders().getFirst("X-ONAP-InvocationID")); + String partnerName = defaultToEmpty(request.getHeaders().getFirst("X-ONAP-PartnerName")); + MDC.put("InvokeTimestamp", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT)); + MDC.put("RequestID", requestID); + MDC.put("InvocationID", invocationID); + MDC.put("PartnerName", partnerName); + MDC.put("ClientIPAddress", defaultToEmpty(request.getRemoteAddress().getAddress())); + MDC.put("ServerFQDN", defaultToEmpty(request.getRemoteAddress().getHostString())); + + header.add(BluePrintConstants.RESPONSE_HEADER_TRANSACTION_ID, requestID); + header.add(BluePrintConstants.RESPONSE_HEADER_MINOR_VERSION, tokens[1]); + header.add(BluePrintConstants.RESPONSE_HEADER_PATCH_VERSION, tokens[2]); + header.add(BluePrintConstants.RESPONSE_HEADER_LATEST_VERSION, appVersion); + } catch (Exception e) { + e.printStackTrace(); + } + + return webFilterChain.filter(serverWebExchange); + } - @Override - public void init(FilterConfig filterConfig) { - //method does nothing + private static String defaultToUUID(String in) { + return in == null ? UUID.randomUUID().toString() : in; } - @Override - public void destroy() { - //method does nothing + private static String defaultToEmpty(Object in) { + return in == null ? "" : in.toString(); } + + } \ No newline at end of file diff --git a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/filters/CorsFilter.java b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/filters/CorsFilter.java deleted file mode 100644 index b97fa178..00000000 --- a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/filters/CorsFilter.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.apps.controllerblueprints.filters; - -import org.springframework.http.HttpMethod; -import org.springframework.stereotype.Component; -import javax.servlet.*; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -/** - * CorsFilter.java Purpose: Provide Configuration Generator CorsFilter Information - * - * @author Brinda Santh - */ -@Component -@WebFilter(asyncSupported = true, urlPatterns = {"/*"}) -@SuppressWarnings("unused") -public class CorsFilter implements Filter { - - public void destroy() { - //method does nothing - } - - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) - throws IOException, ServletException { - - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; - - response.addHeader("Access-Control-Allow-Origin", "*"); - response.addHeader("Access-Control-Allow-Methods", "*"); - response.addHeader("Access-Control-Allow-Headers", - "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range"); - - if (request.getMethod().equals(HttpMethod.OPTIONS.toString())) { - response.addHeader("Access-Control-Max-Age", "1728000"); - response.setStatus(HttpServletResponse.SC_ACCEPTED); - return; - } - chain.doFilter(request, servletResponse); - } - - public void init(FilterConfig fConfig) throws ServletException { - //method does nothing - } - -} \ No newline at end of file diff --git a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/security/ApplicationBasicAuthenticationEntryPoint.java b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/security/ApplicationBasicAuthenticationEntryPoint.java deleted file mode 100644 index e3df3a62..00000000 --- a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/security/ApplicationBasicAuthenticationEntryPoint.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.apps.controllerblueprints.security; - -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; -import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@Component -public class ApplicationBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint { - - @Override - public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) - throws IOException { - response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\""); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); - } - - @Override - public void afterPropertiesSet() throws Exception { - setRealmName("CCSDK-APPS"); - super.afterPropertiesSet(); - } - -} \ No newline at end of file diff --git a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/security/ApplicationSecurityConfigurerAdapter.java b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/security/ApplicationSecurityConfigurerAdapter.java index 3a39d782..334574f7 100644 --- a/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/security/ApplicationSecurityConfigurerAdapter.java +++ b/ms/controllerblueprints/application/src/main/java/org/onap/ccsdk/apps/controllerblueprints/security/ApplicationSecurityConfigurerAdapter.java @@ -1,38 +1,35 @@ /* - * Copyright © 2017-2018 AT&T Intellectual Property. + * 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 + * 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 + * 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. + * 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.apps.controllerblueprints.security; import com.att.eelf.configuration.EELFLogger; import com.att.eelf.configuration.EELFManager; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.server.SecurityWebFilterChain; @SuppressWarnings("unused") -@Configuration -@EnableWebSecurity -public class ApplicationSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { +@EnableWebFluxSecurity +public class ApplicationSecurityConfigurerAdapter { @Value("${basic-auth.user-name}") private String userName; @@ -42,31 +39,24 @@ public class ApplicationSecurityConfigurerAdapter extends WebSecurityConfigurerA private static EELFLogger log = EELFManager.getInstance().getLogger(ApplicationSecurityConfigurerAdapter.class); - @Autowired - private ApplicationBasicAuthenticationEntryPoint authenticationEntryPoint; - - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - log.info("User Id {} and hashed pwd : {}", userName, userHashedPassword); - auth.inMemoryAuthentication() - .withUser(userName).password(userHashedPassword) - .authorities("ROLE_USER"); - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.authorizeRequests() - .antMatchers("/actuator/health").permitAll() - .antMatchers("/**").authenticated() - .and() - .httpBasic() - .authenticationEntryPoint(authenticationEntryPoint); + @Bean + public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception { http.csrf().disable(); + http.authorizeExchange() + .pathMatchers("/webjars/**", "/actuator/**").permitAll() + .anyExchange().authenticated() + .and().httpBasic(); + + return http.build(); } @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); + public MapReactiveUserDetailsService userDetailsService() { + User.UserBuilder userBuilder = User.builder(); + UserDetails defaultUser = userBuilder + .username(userName) + .password(userHashedPassword).roles("USER").build(); + return new MapReactiveUserDetailsService(defaultUser); } } \ No newline at end of file -- cgit 1.2.3-korg