Skip to content

Commit

Permalink
Merge pull request #202 from ttomsu/google-oauth-multi
Browse files Browse the repository at this point in the history
Make all configs use SpinnakerUserDetails
  • Loading branch information
jtk54 committed May 2, 2016
2 parents 249e978 + 74f8975 commit 1922cc1
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ package com.netflix.spinnaker.gate.config

class Headers {
public static final String AUTHENTICATION_REDIRECT_HEADER_NAME = "X-AUTH-REDIRECT-URL"
public static final String OAUTH2_TOKEN_HEADER = "X-AUTH-TOKEN"
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,7 @@ class GateConfig {
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, content-type, authorization");
response.setHeader("Access-Control-Expose-Headers", [Headers.AUTHENTICATION_REDIRECT_HEADER_NAME,
Headers.OAUTH2_TOKEN_HEADER].join(", "))
response.setHeader("Access-Control-Expose-Headers", [Headers.AUTHENTICATION_REDIRECT_HEADER_NAME].join(", "))
chain.doFilter(req, res);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.netflix.spinnaker.gate.controllers

import com.netflix.spinnaker.gate.security.SpinnakerUser
import com.netflix.spinnaker.gate.security.SpinnakerUserDetails
import com.netflix.spinnaker.security.User
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
Expand All @@ -29,12 +30,12 @@ import javax.servlet.http.HttpServletResponse
class AuthController {

@RequestMapping("/user")
User user(@SpinnakerUser User user) {
return user;
User user(@SpinnakerUser SpinnakerUserDetails user) {
user?.spinnakerUser
}

@RequestMapping("/redirect")
void redirect(HttpServletResponse response, @RequestParam to) {
void redirect(HttpServletResponse response, @RequestParam String to) {
response.sendRedirect(to)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ import java.lang.annotation.Target

@Target([ElementType.PARAMETER])
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal
@AuthenticationPrincipal(errorOnInvalidType = true)
@interface SpinnakerUser {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2016 Google, Inc.
*
* 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 com.netflix.spinnaker.gate.security

import com.fasterxml.jackson.annotation.JsonIgnore
import com.netflix.spinnaker.security.User
import groovy.transform.Canonical
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

@Canonical
class SpinnakerUserDetails implements UserDetails {
User spinnakerUser

@Override
List getAuthorities() {
spinnakerUser?.roles?.collect { new SimpleGrantedAuthority(it) }
}

/** Not used **/
@JsonIgnore String username
@JsonIgnore String password

@Override
boolean isAccountNonExpired() { return true }

@Override
boolean isAccountNonLocked() { return true }

@Override
boolean isCredentialsNonExpired() { return true }

@Override
boolean isEnabled() { return true }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.netflix.spinnaker.gate.security.anonymous

import com.netflix.spinnaker.gate.security.AnonymousAccountsService
import com.netflix.spinnaker.gate.security.SpinnakerAuthConfig
import com.netflix.spinnaker.gate.security.SpinnakerUserDetails
import com.netflix.spinnaker.security.User
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
Expand All @@ -37,11 +38,17 @@ class AnonymousConfig extends WebSecurityConfigurerAdapter {
AnonymousAccountsService anonymousAccountsService

void configure(HttpSecurity http) {
def principal = new SpinnakerUserDetails(
spinnakerUser: new User(
email: defaultEmail,
roles: ["anonymous"],
allowedAccounts: anonymousAccountsService.getAllowedAccounts()
)
)

http.anonymous()
.key(key)
.authorities("anonymous")
.principal(new User(email: defaultEmail,
roles: ["anonymous"],
allowedAccounts: anonymousAccountsService.getAllowedAccounts()))
.principal(principal)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.netflix.spinnaker.gate.security.oauth2.client
import com.netflix.spinnaker.gate.security.AnonymousAccountsService
import com.netflix.spinnaker.gate.security.AuthConfig
import com.netflix.spinnaker.gate.security.SpinnakerAuthConfig
import com.netflix.spinnaker.gate.security.SpinnakerUserDetails
import com.netflix.spinnaker.gate.security.rolesprovider.SpinnakerUserRolesProvider
import com.netflix.spinnaker.security.User
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -45,6 +46,7 @@ import org.springframework.security.oauth2.common.exceptions.InvalidTokenExcepti
import org.springframework.security.oauth2.provider.OAuth2Authentication
import org.springframework.security.oauth2.provider.OAuth2Request
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken

@Configuration
@SpinnakerAuthConfig
Expand Down Expand Up @@ -104,27 +106,29 @@ class OAuth2SsoConfig extends OAuth2SsoConfigurerAdapter {
OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
OAuth2Authentication oAuth2Authentication = userInfoTokenServices.loadAuthentication(accessToken)

// TODO(ttomsu): https://github.com/spring-projects/spring-boot/pull/5053 would obviate the need to create a
// custom Authentication object just to override the Principal. Alas, it's not scheduled to be released until
// Spring Boot 1.3.4.
// See also https://github.com/spring-projects/spring-boot/commit/4768faaba771e35301b0ac68abf09cdb0e2f6881,
// which adds an AuthoritiesExtractor, which eliminate the other need for this class (Authorities).
SpinnakerAuthentication spinnakerAuthentication = new SpinnakerAuthentication(AuthorityUtils.createAuthorityList("IM_A_ROLE"))
Map details = oAuth2Authentication.userAuthentication.details as Map
spinnakerAuthentication.principal = new User(

User spinnakerUser = new User(
email: details.email,
firstName: details.given_name,
lastName: details.family_name,
allowedAccounts: anonymousAccountsService.allowedAccounts,
roles: spinnakerUserRolesProvider.loadRoles(details.email)
)
spinnakerAuthentication.setAuthenticated(true)

SpinnakerUserDetails spinnakerUserDetails = new SpinnakerUserDetails(spinnakerUser: spinnakerUser)
// Uses the constructor that sets authenticated = true.
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(
spinnakerUserDetails,
null /* credentials */,
spinnakerUserDetails.authorities)


// impl copied from userInfoTokenServices
OAuth2Request storedRequest = new OAuth2Request(null, sso.clientId, null, true /*approved*/,
null, null, null, null, null);

return new OAuth2Authentication(storedRequest, spinnakerAuthentication)
return new OAuth2Authentication(storedRequest, authentication)
}

@Override
Expand All @@ -133,22 +137,4 @@ class OAuth2SsoConfig extends OAuth2SsoConfigurerAdapter {
}
}
}

/**
* Simple implementation to hold our Kork User as the Principal, which is used all over the framework.
*/
static class SpinnakerAuthentication extends AbstractAuthenticationToken {
def credentials
User principal

/**
* Creates a token with the supplied array of authorities.
*
* @param authorities the collection of <tt>GrantedAuthority</tt>s for the
* principal represented by this authentication object.
*/
SpinnakerAuthentication(Collection<? extends GrantedAuthority> authorities) {
super(authorities)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2016 Google, Inc.
*
* 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 com.netflix.spinnaker.gate.security.x509

import com.netflix.spinnaker.gate.security.AnonymousAccountsService
import com.netflix.spinnaker.gate.security.SpinnakerUserDetails
import com.netflix.spinnaker.security.User
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
import org.springframework.stereotype.Component

import java.security.cert.X509Certificate

/**
* This class is similar to a UserDetailService, but instead of passing in a username to loadUserDetails,
* it passess in a token containing the x509 certificate. A user can control the principal through the
* `spring.x509.subjectPrincipalRegex` property.
*/
@Component
class X509AuthenticationUserDetailsService implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {

@Autowired
AnonymousAccountsService anonymousAccountsService

@Override
UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) throws UsernameNotFoundException {
if (!(token.credentials instanceof X509Certificate)) {
return null
}

return new SpinnakerUserDetails(
spinnakerUser: new User(email: token.principal,
allowedAccounts: anonymousAccountsService.allowedAccounts))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,35 @@

package com.netflix.spinnaker.gate.security.x509

import com.netflix.spinnaker.gate.security.AnonymousAccountsService
import com.netflix.spinnaker.gate.security.AuthConfig
import com.netflix.spinnaker.gate.security.SpinnakerAuthConfig
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
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.WebSecurityConfigurerAdapter
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter

@ConditionalOnExpression('${x509.enabled:false}')
@ConditionalOnExpression('${spring.x509.enabled:false}')
@SpinnakerAuthConfig
@Configuration
@EnableWebMvcSecurity
class X509Config extends WebSecurityConfigurerAdapter {

@Autowired
AnonymousAccountsService anonymousAccountsService

@Value('${spring.x509.subjectPrincipalRegex:}')
String subjectPrincipalRegex

@Override
void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(new X509AuthenticationProvider(anonymousAccountsService))
}
@Autowired
X509AuthenticationUserDetailsService x509AuthenticationUserDetailsService

@Override
void configure(HttpSecurity http) {
// Specify which endpoints to lock down.
AuthConfig.configure(http)
http.x509().authenticationUserDetailsService(x509AuthenticationUserDetailsService)

// We don't use http.x509() here because there is no way to override it to use our
// Spinnaker User as the Principal. The {@link X509AuthenticationProvider} configured
// above (in tandem with this config) enable us to insert this custom Principal.
def filter = new X509AuthenticationFilter()
filter.setAuthenticationManager(authenticationManager())
http.addFilter(filter)
if (subjectPrincipalRegex) {
http.x509().subjectPrincipalRegex(subjectPrincipalRegex)
}
}
}

0 comments on commit 1922cc1

Please sign in to comment.