13

I'm trying to port my Spring boot 1.5 application to Spring Boot 2

Right now I'm unable to obtain OAuth2 access token.

This is the code I have successfully used with Spring Boot 1.5:

public static String loginAndGetAccessToken(String username, String password, int port) {

    ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
    resourceDetails.setUsername(username);
    resourceDetails.setPassword(password);
    resourceDetails.setAccessTokenUri(String.format("http://localhost:%d/api/oauth/token", port));
    resourceDetails.setClientId("clientapp");
    resourceDetails.setClientSecret("123456");
    resourceDetails.setGrantType("password");
    resourceDetails.setScope(Arrays.asList("read", "write"));

    DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();

    OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
    restTemplate.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));

    return restTemplate.getAccessToken().toString();
}

it is fails with the following exception:

java.lang.IllegalStateException: An OAuth 2 access token must be obtained or an exception thrown.
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:124)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)

Looks like http://localhost:%d/api/oauth/token endpoint is secured right now and can't be accessible

This is my configuration:

OAuth2ServerConfig

@Configuration
public class OAuth2ServerConfig {

    public static final String RESOURCE_ID = "restservice";
    public static final String EXAMPLE_CLIENT_ID = "example_client_id";

    @Value("${jwt.access.token.converter.signing.key}")
    private String jwtAccessTokenConverterSigningKey;

    @Value("${jwt.access.token.validity.seconds}")
    private int accessTokenValiditySeconds;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
        return tokenServices;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new UserAwareAccessTokenConverter();

        converter.setSigningKey(jwtAccessTokenConverterSigningKey);

        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
        userTokenConverter.setUserDetailsService(userDetailsService);
        accessTokenConverter.setUserTokenConverter(userTokenConverter);

        converter.setAccessTokenConverter(accessTokenConverter);

        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Value("${jwt.access.token.validity.seconds}")
        private int accessTokenValiditySeconds;

        @Autowired
        private TokenStore tokenStore;

        @Autowired
        private TokenEnhancer tokenEnhancer;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // @formatter:off
            endpoints
                .tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancer)
                .authenticationManager(this.authenticationManager);
            // @formatter:on
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // @formatter:off
            clients
                .inMemory()
                    .withClient("clientapp")
                        .authorizedGrantTypes("password","refresh_token")
                        .authorities("ROLE_CLIENT")
                        .scopes("read", "write")
                        .resourceIds(RESOURCE_ID)
                        .secret("123456")
                    .and()
                    .withClient(EXAMPLE_CLIENT_ID)
                        .authorizedGrantTypes("implicit")
                        .scopes("read", "write")
                        .autoApprove(true)
                    .and()
                        .withClient("my-trusted-client")
                        .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                        .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                        .scopes("read", "write", "trust")
                        .accessTokenValiditySeconds(accessTokenValiditySeconds);
            // @formatter:on
        }

    }

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Autowired
        private ResourceServerTokenServices tokenService;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            // @formatter:off
            resources           
                .resourceId(RESOURCE_ID)
                .tokenServices(tokenService);
            // @formatter:on
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http                
                .antMatcher("/v1.0/**").authorizeRequests()
                .antMatchers("/v1.0/search/**").permitAll()
                .antMatchers("/v1.0/users/**").permitAll()
                .antMatchers("/v1.0/decisions/**").permitAll()
                .antMatchers("/v1.0/votes/**").permitAll()
                .antMatchers("/v1.0/likes/**").permitAll()
                .antMatchers("/v1.0/likeables/**").permitAll()
                .antMatchers("/v1.0/flags/**").permitAll()
                .antMatchers("/v1.0/flagtypes/**").permitAll()
                .antMatchers("/v1.0/flaggables/**").permitAll()
                .antMatchers("/v1.0/comments/**").permitAll()
                .antMatchers("/v1.0/commentables/**").permitAll()
                .antMatchers("/v1.0/subscribables/**").permitAll()
                .antMatchers("/v1.0/favoritables/**").permitAll()
                .antMatchers("/v1.0/import/**").permitAll()
                .antMatchers("/v1.0/tags/**").permitAll()
                .antMatchers("/v1.0/medias/**").permitAll()
                .antMatchers("/swagger**").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(STATELESS); 
            // @formatter:on
        }

    }

}

WebMvcConfig

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/login").setViewName("login");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");

        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

}

WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Value("${logout.success.url}")
    private String logoutSuccessUrl;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // @formatter:off
        http
            .cors()
        .and()
            .csrf().ignoringAntMatchers("/v1.0/**", "/logout")
        .and()
            .authorizeRequests()

            .antMatchers("/oauth/authorize").authenticated()
            //Anyone can access the urls
            .antMatchers("/images/**").permitAll()
            .antMatchers("/signin/**").permitAll()
            .antMatchers("/v1.0/**").permitAll()
            .antMatchers("/auth/**").permitAll()
            .antMatchers("/actuator/health").permitAll()
            .antMatchers("/actuator/**").hasAuthority(Permission.READ_ACTUATOR_DATA)
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .failureUrl("/login?error=true")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
            .and()
                .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessUrl(logoutSuccessUrl)
                    .permitAll();
        // @formatter:on
    }

    /**
     * Configures the authentication manager bean which processes authentication requests.
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
        configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

What should be changed there in order to get it working with Spring Boot 2 ?

Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
alexanoid
  • 24,051
  • 54
  • 210
  • 410
  • There's quite a lot of code here, and much of it has nothing to do with OAuth. There's also some pieces missing. A _complete_ and _minimal_ sample would make diagnosis much easier – Andy Wilkinson Jul 30 '17 at 07:46
  • Looks like API secured with OAuth2 overlapped with a web security config at Spring Boot 2 this is why I have added both of them. BTW - this config works fine at Spring Boot 1.5 – alexanoid Jul 30 '17 at 08:03
  • 1
    What you've shown here won't work with any version of Spring Boot as it's incomplete. For example, you haven't shared your dependencies or any of your configuration properties. If you don't spend the time to share a minimal complete example, it's unlikely that people will spend time trying to fill in the gaps in order to help you. – Andy Wilkinson Jul 30 '17 at 17:45
  • I have reverted my changes to SB1.5.7 will wait until working version of SB2.0 final – alexanoid Jul 30 '17 at 18:08
  • @AndyWilkinson I have created a separate question for my new attempt to migrate to Spring Boot 2.0.0.M4 and provided GitHub test project there that reproduces the issue https://stackoverflow.com/questions/46558921/spring-boot-2-0-0-m4-oauth2-token-endpoint-throws-org-springframework-web-httpre – alexanoid Oct 04 '17 at 07:18
  • Thank you very much, one last thing; which dependency did you use for oauth2 and jwt? I am using compile('org.springframework.cloud:spring-cloud-starter-oauth2') and compile("org.springframework.security:spring-security-jwt"). – turgos Feb 24 '18 at 15:11
  • Please see my dependency tree https://files.fm/u/2vqpefe8 – alexanoid Feb 24 '18 at 18:06
  • Spring released [Spring Security OAuth Boot 2 Auto-config 2.0.0](https://spring.io/blog/2018/03/01/spring-security-oauth-boot-2-auto-config-2-0-0-released). See the documentation at [OAuth2 Autoconfig](https://docs.spring.io/spring-security-oauth2-boot/docs/current-SNAPSHOT/reference/htmlsingle/). – Philippe Mar 08 '18 at 19:46

2 Answers2

2

OAuth2ServerConfig

import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import com.decisionwanted.api.security.authentication.UserAwareAccessTokenConverter;
import com.decisionwanted.domain.service.user.social.UserDetailsService;

@Configuration
public class OAuth2ServerConfig {

    public static final String RESOURCE_ID = "restservice";
    public static final String CLIENT_ID = "client_id";

    @Value("${jwt.access.token.converter.signing.key}")
    private String jwtAccessTokenConverterSigningKey;

    @Value("${jwt.access.token.validity.seconds}")
    private int accessTokenValiditySeconds;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds);
        return tokenServices;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new UserAwareAccessTokenConverter();

        converter.setSigningKey(jwtAccessTokenConverterSigningKey);

        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
        userTokenConverter.setUserDetailsService(userDetailsService);
        accessTokenConverter.setUserTokenConverter(userTokenConverter);

        converter.setAccessTokenConverter(accessTokenConverter);

        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Value("${jwt.access.token.validity.seconds}")
        private int accessTokenValiditySeconds;

        @Autowired
        private TokenStore tokenStore;

        @Autowired
        private TokenEnhancer tokenEnhancer;

        private PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // @formatter:off
            endpoints
                .tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancer)
                .authenticationManager(this.authenticationManager);
            // @formatter:on
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // @formatter:off
            clients
                .inMemory()
                    .withClient("clientapp")
                        .authorizedGrantTypes("password","refresh_token")
                        .authorities("ROLE_CLIENT")
                        .scopes("read", "write")
                        .resourceIds(RESOURCE_ID)
                        .secret(passwordEncoder.encode("changeit"))
                    .and()
                    .withClient(CLIENT_ID)
                        .authorizedGrantTypes("implicit")
                        .scopes("read", "write")
                        .autoApprove(true)
                    .and()
                        .withClient("my-trusted-client")
                        .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                        .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                        .scopes("read", "write", "trust")
                        .accessTokenValiditySeconds(accessTokenValiditySeconds);
            // @formatter:on
        }

    }

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Autowired
        private ResourceServerTokenServices tokenService;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            // @formatter:off
            resources           
                .resourceId(RESOURCE_ID)
                .tokenServices(tokenService);
            // @formatter:on
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http                
                .antMatcher("/v1.0/**").authorizeRequests()
                .antMatchers("/v1.0/search/**").permitAll()
                .antMatchers("/v1.0/users/**").permitAll()
                .antMatchers("/v1.0/decisiongroups/**").permitAll()
                .antMatchers("/swagger**").permitAll()
                .anyRequest().authenticated()
                .and()
                    .cors()
                .and()
                    .csrf().disable()
                .sessionManagement()
                    .sessionCreationPolicy(STATELESS); 
            // @formatter:on
        }

    }

}

WebMvcConfig

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/login").setViewName("login");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");

        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

}

WebSecurityConfig

import java.util.Arrays;

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.authentication.AuthenticationManager;
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.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import com.decisionwanted.domain.model.neo4j.security.permission.Permission;
import com.decisionwanted.domain.service.user.social.UserDetailsService;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Value("${logout.success.url}")
    private String logoutSuccessUrl;

    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // @formatter:off
        http
            .cors()
        .and()
            .csrf().ignoringAntMatchers("/v1.0/**", "/logout")
        .and()
            .authorizeRequests()

            .antMatchers("/oauth/authorize").authenticated()
            //Anyone can access the urls
            .antMatchers("/images/**").permitAll()
            .antMatchers("/signin/**").permitAll()
            .antMatchers("/v1.0/**").permitAll()
            .antMatchers("/auth/**").permitAll()
            .antMatchers("/actuator/health").permitAll()
            .antMatchers("/actuator/**").hasAuthority(Permission.READ_ACTUATOR_DATA)
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .failureUrl("/login?error=true")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
            .and()
                .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessUrl(logoutSuccessUrl)
                    .permitAll();
        // @formatter:on
    }

    /**
     * Configures the authentication manager bean which processes authentication requests.
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
        configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

SecurityTestUtils

import java.util.Arrays;

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;

public class SecurityTestUtils {

    public static final String AUTH_HEADER_NAME = "Authorization";
    public static final String AUTH_COOKIE_NAME = "AUTH-TOKEN";

    public static String loginAndGetAccessToken(String username, String password, int port) {

        ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
        resourceDetails.setUsername(username);
        resourceDetails.setPassword(password);
        resourceDetails.setAccessTokenUri(String.format("http://localhost:%d/api/oauth/token", port));
        resourceDetails.setClientId("clientapp");
        resourceDetails.setClientSecret("changeit");
        resourceDetails.setGrantType("password");
        resourceDetails.setScope(Arrays.asList("read", "write"));

        DefaultOAuth2ClientContext clientContext = new DefaultOAuth2ClientContext();

        OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
        restTemplate.setMessageConverters(Arrays.asList(new MappingJackson2HttpMessageConverter()));

        return restTemplate.getAccessToken().toString();
    }

}
alexanoid
  • 24,051
  • 54
  • 210
  • 410
1

Try in your spring security config to permit this url /api/oauth/token I noticed it seemed to not define what to do with this.

Pasha Utt
  • 41
  • 1
  • 4