hacking the grails spring security plugins
Post on 01-Sep-2014
9.287 Views
Preview:
DESCRIPTION
TRANSCRIPT
Burt BeckwithSpringSource
Hacking the Grails Spring Security Plugins
2CONFIDENTIAL 2CONFIDENTIAL 2
3CONFIDENTIAL 3CONFIDENTIAL
web.xml
4CONFIDENTIAL 4CONFIDENTIAL
web.xml
Spring Security is implemented as a filter chain, so the
<filter-mapping> elements are the important ones:
• charEncodingFilter
• hiddenHttpMethod
• grailsWebRequest
• springSecurityFilterChain
• reloadFilter
• sitemesh
• urlMapping
5CONFIDENTIAL 5CONFIDENTIAL
web.xml
Spring Security is implemented as a filter chain, so the
<filter-mapping> elements are the important ones:
• charEncodingFilter
• hiddenHttpMethod
• grailsWebRequest
• springSecurityFilterChain
• reloadFilter
• sitemesh
• urlMapping
6CONFIDENTIAL 6CONFIDENTIAL
web.xml - charEncodingFilter
org.springframework.web.filter.DelegatingFilterProxy
Uses the "characterEncodingFilter" bean
(org.springframework.web.filter.CharacterEncodingFilter) defined
in applicationContext.xml (the “parent” context)
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
7CONFIDENTIAL 7CONFIDENTIAL
web.xml - hiddenHttpMethod
org.codehaus.groovy.grails.web.filters.HiddenHttpMethodFilter
<g:form controller="book" method="DELETE">
See section “13.1 REST” in the docs
String httpMethod = getHttpMethodOverride(request);filterChain.doFilter( new HttpMethodRequestWrapper(httpMethod, request), response);
8CONFIDENTIAL 8CONFIDENTIAL
web.xml - grailsWebRequest
org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter
LocaleContextHolder.setLocale(request.getLocale());GrailsWebRequest webRequest = new GrailsWebRequest( request, response, getServletContext());configureParameterCreationListeners(webRequest);
try { WebUtils.storeGrailsWebRequest(webRequest);
// Set the flash scope instance to its next state FlashScope fs = webRequest.getAttributes().getFlashScope(request); fs.next();
filterChain.doFilter(request, response);}finally { webRequest.requestCompleted(); WebUtils.clearGrailsWebRequest(); LocaleContextHolder.setLocale(null);}
9CONFIDENTIAL 9CONFIDENTIAL
web.xml - springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
Uses the "springSecurityFilterChain" bean defined by the plugin
(org.springframework.security.web.FilterChainProxy)
10CONFIDENTIAL 10CONFIDENTIAL
web.xml - springSecurityFilterChain
Typical filter beans:
• securityContextPersistenceFilter
• logoutFilter
• authenticationProcessingFilter
• securityContextHolderAwareRequestFilter
• rememberMeAuthenticationFilter
• anonymousAuthenticationFilter
• exceptionTranslationFilter
• filterInvocationInterceptor
11CONFIDENTIAL 11CONFIDENTIAL
web.xml - reloadFilter
org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter
No longer used in 2.0+
“Copies resources from the source on content change and manages
reloading if necessary.”
GrailsApplication application = (GrailsApplication)context.getBean( GrailsApplication.APPLICATION_ID);if (!application.isInitialised()) { application.rebuild(); GrailsRuntimeConfigurator config = new GrailsRuntimeConfigurator( application, parent); config.reconfigure(context, getServletContext(), true);}
12CONFIDENTIAL 12CONFIDENTIAL
web.xml - sitemesh
org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter
Subclass of com.opensymphony.sitemesh.webapp.SiteMeshFilter
Renders Sitemesh templates/layouts
13CONFIDENTIAL 13CONFIDENTIAL
web.xml - urlMapping
org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter
Matches requested url to the correct controller/action/view/error code
Based on mappings in grails-app/conf/UrlMappings.groovy
14CONFIDENTIAL 14CONFIDENTIAL
springSecurityFilterChain
15CONFIDENTIAL 15CONFIDENTIAL
springSecurityFilterChain - securityContextPersistenceFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter
Retrieves the SecurityContext from the HTTP session (under the
”SPRING_SECURITY_CONTEXT” key) or creates a new empty one
Stores the context in the holder: SecurityContextHolder.setContext()
Removes the context after request
16CONFIDENTIAL 16CONFIDENTIAL
springSecurityFilterChain - logoutFilter
org.codehaus.groovy.grails.plugins.springsecurity.MutableLogoutFilter
If uri is ”/j_spring_security_logout” calls
handler.logout(request, response, auth) for each
o.s.s.web.authentication.logout.LogoutHandler and redirects to post-
logout url (by default ”/”)
17CONFIDENTIAL 17CONFIDENTIAL
springSecurityFilterChain - authenticationProcessingFilter
org.codehaus.groovy.grails.plugins.springsecurity.RequestHolderAuthenticationFilter
(extends o.s.s.web.authentication.UsernamePasswordAuthenticationFilter)
Sets the request and response in the SecurityRequestHolder
ThreadLocal fields (custom plugin functionality)
If uri is ”/j_spring_security_check” calls
attemptAuthentication()
18CONFIDENTIAL 18CONFIDENTIAL
springSecurityFilterChain - securityContextHolderAwareRequestFilter
o.s.s.web.servletapi.SecurityContextHolderAwareRequestFilter
Configures a request wrapper which implements the servlet API security
methods (getRemoteUser, getUserPrincipal, isUserInRole)
chain.doFilter(new SecurityContextHolderAwareRequestWrapper( request, rolePrefix), response);
19CONFIDENTIAL 19CONFIDENTIAL
springSecurityFilterChain - rememberMeAuthenticationFilter
o.s.s.web.authentication.rememberme.RememberMeAuthenticationFilter
If not logged in, attempt to login from the
remember-me cookie
20CONFIDENTIAL 20CONFIDENTIAL
springSecurityFilterChain - anonymousAuthenticationFilter
o.s.s.web.authentication.AnonymousAuthenticationFilter
If not logged in creates an AnonymousAuthenticationToken and
registers it as the Authentication in the SecurityContext
https://secure.flickr.com/photos/gaelx/5445598436/
21CONFIDENTIAL 21CONFIDENTIAL
springSecurityFilterChain - exceptionTranslationFilter
o.s.s.web.access.ExceptionTranslationFilter
Handles AccessDeniedException and AuthenticationException
For AuthenticationException will invoke the
authenticationEntryPoint
For AccessDeniedException invoke the
authenticationEntryPoint if anonymous, otherwise delegate to
o.s.s.web.access.AccessDeniedHandler
22CONFIDENTIAL 22CONFIDENTIAL
springSecurityFilterChain - filterInvocationInterceptor
o.s.s.web.access.intercept.FilterSecurityInterceptor
Determines the o.s.s.access.SecurityConfig collection for the
request from FilterInvocationSecurityMetadataSource
(AnnotationFilterInvocationDefinition,
InterceptUrlMapFilterInvocationDefinition, or
RequestmapFilterInvocationDefinition)
23CONFIDENTIAL 23CONFIDENTIAL
springSecurityFilterChain - filterInvocationInterceptor
Delegates to an AccessDecisionManager
(org.codehaus.groovy.grails.plugins.springsecurity.
AuthenticatedVetoableDecisionManager) to call each registered
o.s.s.access.AccessDecisionVoter
24CONFIDENTIAL 24CONFIDENTIAL
springSecurityFilterChain - filterInvocationInterceptor
o.s.s.access.vote.AuthenticatedVoter works with
“IS_AUTHENTICATED_FULLY”, “IS_AUTHENTICATED_REMEMBERED”,
and “IS_AUTHENTICATED_ANONYMOUSLY”
25CONFIDENTIAL 25CONFIDENTIAL
springSecurityFilterChain - filterInvocationInterceptor
o.s.s.access.vote.RoleHierarchyVoter finds the
GrantedAuthority instances from the Authentication, checks
for matches with required roles
26CONFIDENTIAL 26CONFIDENTIAL
springSecurityFilterChain - filterInvocationInterceptor
org.codehaus.groovy.grails.plugins.springsecurity.
WebExpressionVoter looks for a
WebExpressionConfigAttribute and evaluates its expression if
found
27CONFIDENTIAL 27CONFIDENTIAL
springSecurityFilterChain - filterInvocationInterceptor
Expression Description
hasRole([role]) Returns true if the current principal has the specified role.
hasAnyRole([role1,role2]) Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings)
principal Allows direct access to the principal object representing the current user
authentication Allows direct access to the current Authentication object obtained from the SecurityContext
permitAll Always evaluates to true
denyAll Always evaluates to false
isAnonymous() Returns true if the current principal is an anonymous user
isRememberMe() Returns true if the current principal is a remember-me user
isAuthenticated() Returns true if the user is not anonymous
isFullyAuthenticated() Returns true if the user is not an anonymous or a remember-me user
Common built-in expressions
28CONFIDENTIAL 28CONFIDENTIAL
springSecurityFilterChain - filterInvocationInterceptor
this is caught by the exceptionTranslationFilter
If the Authentication is anonymous, stores a SavedRequest in the
HTTP session and calls authenticationEntryPoint.commence(
request, response, reason)
The authenticationEntryPoint is an
org.codehaus.groovy.grails.plugins.springsecurity.
AjaxAwareAuthenticationEntryPoint
This issues a redirect to the login page, by default /login/auth
if (denyCount > 0) { throw new AccessDeniedException("Access is denied");}
29CONFIDENTIAL 29CONFIDENTIAL
Customizing the Plugin
30CONFIDENTIAL 30CONFIDENTIAL
Customizing the Plugin
Traditional Spring Security uses XML with namespaced beans:
<beans:beans xmlns="http://www.springframework.org/schema/security" ...>
<http auto-config="true"> <intercept-url pattern="/admin/**" access="ROLE_ADMIN" /> </http> <authentication-manager> <authentication-provider> <jdbc-user-service data-source-ref="dataSource" /> </authentication-provider> </authentication-manager> </beans:beans>
31CONFIDENTIAL 31CONFIDENTIAL
Customizing the Plugin
Simple; not always clear how to customize; many options aren't exposed
In contrast, the plugin explicitly defines every bean
See SpringSecurityCoreGrailsPlugin.groovy for the details
32CONFIDENTIAL 32CONFIDENTIAL
Overriding Beans
33CONFIDENTIAL 33CONFIDENTIAL
Overriding Beans
accessDecisionManager
accessDeniedHandler
anonymousAuthenticationFilter
anonymousAuthenticationProvider
authenticatedVoter
authenticationDetailsSource
authenticationEntryPoint
authenticationEventPublisher
authenticationFailureHandler
authenticationManager
authenticationProcessingFilter
authenticationSuccessHandler
authenticationTrustResolver
authenticationUserDetailsService
daoAuthenticationProvider
exceptionTranslationFilter
filterInvocationInterceptor
logoutFilter
logoutHandlers
logoutSuccessHandler
objectDefinitionSource
passwordEncoder
portMapper
portResolver
postAuthenticationChecks
preAuthenticationChecks
redirectStrategy
rememberMeAuthenticationFilter
rememberMeAuthenticationProvider
rememberMeServices
requestCache
roleHierarchy
roleVoter
runAsManager
saltSource
securityContextHolderAwareRequestFilter
securityContextLogoutHandler
securityContextPersistenceFilter
securityEventListener
sessionAuthenticationStrategy
springSecurityFilterChain
tokenRepository
userCache
userDetailsService
webExpressionHandler
webExpressionVoter
webInvocationPrivilegeEvaluator
34CONFIDENTIAL 34CONFIDENTIAL
Overriding Beans
Building the Spring ApplicationContext
Core pluginsCore plugins Installed pluginsInstalled plugins resources.groovyresources.groovy
35CONFIDENTIAL 35CONFIDENTIAL
Overriding Beans
Gross oversimplifications:
• The ApplicationContext is like a Map; the
keys are bean names and the values are bean
instances
• Defining a bean with the same name as an earlier
bean replaces the previous, just like
Map.put(key, value) does
36CONFIDENTIAL 36CONFIDENTIAL
Overriding Beans
To change how a bean works:
• Subclass the current class
• Or subclass a similar class that's closer
to what you need
• Or implement the interface directly
• Then register yours in grails-app/
conf/spring/resources.groovy
37CONFIDENTIAL 37CONFIDENTIAL
Overriding Beans - resources.groovy
beans = {
saltSource(com.foo.bar.MySaltSource) { // bean properties }
userDetailsService(com.foo.bar.MyUserDetailsService) { // bean properties }
passwordEncoder(com.foo.bar.MyPasswordEncoder) { // bean properties }
}
38CONFIDENTIAL 38CONFIDENTIAL
Customizing Bean Properties
39CONFIDENTIAL 39CONFIDENTIAL
Customizing Bean Properties
In addition to declaring every bean, nearly all properties are configured
from default values in the plugin's
grails-app/conf/DefaultSecurityConfig.groovy
DO NOT EDIT DefaultSecurityConfig.groovy
40CONFIDENTIAL 40CONFIDENTIAL
Customizing Bean Properties
All properties can be overridden in app's Config.groovy
• Be sure to add the grails.plugins.springsecurity prefix
Don't replace a bean if all you need to change is one or more properties
41CONFIDENTIAL 41CONFIDENTIAL
Customizing Bean Properties
active
adh.ajaxErrorPage
adh.errorPage
ajaxHeader
anon.key
anon.userAttribute
apf.allowSessionCreation
apf.continueChainBeforeSuccessfulAuthentication
apf.filterProcessesUrl
apf.passwordParameter
apf.postOnly
apf.usernameParameter
atr.anonymousClass
atr.rememberMeClass
auth.ajaxLoginFormUrl
auth.forceHttps
auth.loginFormUrl
auth.useForward
authenticationDetails.authClass
authority.className
authority.nameField
basic.realmName
cacheUsers
controllerAnnotations.lowercase
controllerAnnotations.matcher
controllerAnnotations.staticRules
dao.hideUserNotFoundExceptions
dao.reflectionSaltSourceProperty
digest.createAuthenticatedToken
digest.key
digest.nonceValiditySeconds
digest.passwordAlreadyEncoded
digest.realmName
digest.useCleartextPasswords
failureHandler.ajaxAuthFailUrl
failureHandler.defaultFailureUrl
failureHandler.exceptionMappings
failureHandler.useForward
filterChain.stripQueryStringFromUrls
interceptUrlMap
ipRestrictions
logout.afterLogoutUrl
logout.filterProcessesUrl
logout.handlerNames
password.algorithm
password.bcrypt.logrounds
password.encodeHashAsBase64
portMapper.httpPort
portMapper.httpsPort
providerManager.eraseCredentialsAfterAuthentication
providerNames
redirectStrategy.contextRelative
registerLoggerListener
rejectIfNoRule
rememberMe.alwaysRemember
rememberMe.cookieName
rememberMe.key
rememberMe.parameter
rememberMe.persistent
rememberMe.persistentToken.domainClassName
rememberMe.persistentToken.seriesLength
rememberMe.persistentToken.tokenLength
rememberMe.tokenValiditySeconds
rememberMe.useSecureCookie
requestCache.createSession
requestCache.onlyOnGet
requestMap.className
requestMap.configAttributeField
requestMap.urlField
roleHierarchy
secureChannel.definition
securityConfigType
sessionFixationPrevention.alwaysCreateSession
sessionFixationPrevention.migrate
successHandler.ajaxSuccessUrl
successHandler.alwaysUseDefault
successHandler.defaultTargetUrl
successHandler.targetUrlParameter
successHandler.useReferer
switchUser.exitUserUrl
switchUser.switchFailureUrl
switchUser.switchUserUrl
switchUser.targetUrl
useBasicAuth
useDigestAuth
useHttpSessionEventPublisher
userLookup.accountExpiredPropertyName
userLookup.accountLockedPropertyName
userLookup.authoritiesPropertyName
userLookup.authorityJoinClassName
userLookup.enabledPropertyName
userLookup.passwordExpiredPropertyName
userLookup.passwordPropertyName
userLookup.userDomainClassName
userLookup.usernamePropertyName
useSecurityEventListener
useSessionFixationPrevention
useSwitchUserFilter
useX509
voterNames
x509.checkForPrincipalChanges
x509.continueFilterChainOnUnsuccessfulAuthentication
x509.invalidateSessionOnPrincipalChange
x509.subjectDnRegex
x509.throwExceptionWhenTokenRejected
42CONFIDENTIAL 42CONFIDENTIAL
Customizing Bean Properties
grails.plugins.springsecurity.userLookup.userDomainClassName = '…'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = '…'
grails.plugins.springsecurity.authority.className = '…'
grails.plugins.springsecurity.auth.loginFormUrl = '/log-in'
grails.plugins.springsecurity.apf.filterProcessesUrl = '/authenticate'
grails.plugins.springsecurity.logout.afterLogoutUrl = '/home'
grails.plugins.springsecurity.userLookup.usernamePropertyName = 'email'
grails-app/conf/Config.groovy:
43CONFIDENTIAL 43CONFIDENTIAL
Customizing Bean Properties
Can also configure beans in BootStrap.groovy, e.g. to avoid
redefining a whole bean in resources.groovy just to change one
dependency:
class BootStrap {
def authenticationProcessingFilter def myAuthenticationFailureHandler
def init = { servletContext -> authenticationProcessingFilter.authenticationFailureHandler = myAuthenticationFailureHandler }
}
44CONFIDENTIAL 44CONFIDENTIAL
Custom UserDetailsService
45CONFIDENTIAL 45CONFIDENTIAL
Custom UserDetailsService
Very common customization
Documented in the plugin docs in section
“11 Custom UserDetailsService”
46CONFIDENTIAL 46CONFIDENTIAL
Custom UserDetailsService
General workflow:
• Create a custom o.s.s.core.userdetails.UserDetailsService
(o.c.g.g.p.s.GrailsUserDetailsService) implementation
• Directly implement the interface (best)
• or extend org.codehaus.groovy.grails.plugins.springsecurity.
GormUserDetailsService (ok, but usually cleaner to implement the
interface)
47CONFIDENTIAL 47CONFIDENTIAL
Custom UserDetailsService
General workflow (cont.):
• Create a custom implementation of o.s.s.core.userdetails.UserDetails
• Extend org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser
• or extend o.s.s.core.userdetails.User
• or directly implement the interface
48CONFIDENTIAL 48CONFIDENTIAL
Custom UserDetailsService
General workflow (cont.):
• Register the bean override in grails-app/conf/spring/resources.groovy:
import com.mycompany.myapp.MyUserDetailsService
beans = { userDetailsService(MyUserDetailsService)}
49CONFIDENTIAL 49CONFIDENTIAL
Custom AuthenticationProvider
50CONFIDENTIAL 50CONFIDENTIAL
Custom AuthenticationProvider
General workflow:
• Create a custom o.s.s.authentication.AuthenticationProvider
implementation
• Extend one that's similar, e.g.
o.s.s.authentication.dao.DaoAuthenticationProvider
• or directly implement the interface
51CONFIDENTIAL 51CONFIDENTIAL
Custom AuthenticationProvider
General workflow (cont.):
• You'll probably want a custom o.s.s.core.Authentication
• Extend an existing implementation, e.g.
o.s.s.authentication.UsernamePasswordAuthenticationToken
• or directly implement the interface
52CONFIDENTIAL 52CONFIDENTIAL
Custom AuthenticationProvider
General workflow (cont.):
• If you're using a custom Authentication, you'll need a filter to create it
• Create a custom javax.servlet.Filter implementation
• Best to extend org.springframework.web.filter.GenericFilterBean
• or one that's similar, e.g.
o.s.s.web.authentication.UsernamePasswordAuthenticationFilter
• Or directly implement the interface
53CONFIDENTIAL 53CONFIDENTIAL
Custom AuthenticationProvider
Registering the custom classes
• If you're replacing functionality, register bean overrides in resources.groovy
• If you're adding an alternate authentication option (e.g. for only some URLs)
• Register the beans in resources.groovy
• Register the provider as described in section “10 Authentication Providers” of
the docs
• Register the filter in its position as described in section “16 Filters” of the docs
54CONFIDENTIAL 54CONFIDENTIAL
Custom Post-logout Behavior
55CONFIDENTIAL 55CONFIDENTIAL
Custom Post-logout Behavior
Recall that logout is processed by MutableLogoutFilter after request
for /j_spring_security_logout
handler.logout(request, response, auth) is called for each
LogoutHandler
then redirect to post-logout url (by default ”/”)
56CONFIDENTIAL 56CONFIDENTIAL
Custom Post-logout Behavior
The default LogoutHandler implementations are
• o.s.s.web.authentication.rememberme.TokenBasedRememberMeServices
• Deletes the remember-me cookie
• o.s.s.web.authentication.logout.SecurityContextLogoutHandler
• Invalidates the HttpSession
• Removes the SecurityContext from the SecurityContextHolder
57CONFIDENTIAL 57CONFIDENTIAL
Custom Post-logout Behavior
Redirect is triggered by
• logoutSuccessHandler.onLogoutSuccess(request, response,
auth)
• LogoutSuccessHandler is an instance of
o.s.s.web.authentication.logout.SimpleUrlLogoutSuccessHandler
58CONFIDENTIAL 58CONFIDENTIAL
Custom Post-logout Behavior
To add logic to dynamically determine redirect url:
• Subclass SimpleUrlLogoutSuccessHandler
• Since the Authentication isn't available in determineTargetUrl, override
onLogoutSuccess and set it in a ThreadLocal
59CONFIDENTIAL 59CONFIDENTIAL
Custom Post-logout Behavior
To add logic to dynamically determine redirect url (cont.):
private static final ThreadLocal<Authentication> AUTH_HOLDER = new ThreadLocal<Authentication>()…
void onLogoutSuccess(...) throws ... { AUTH_HOLDER.set authentication try { super.handle(request, response, authentication) } finally { AUTH_HOLDER.remove() }}
60CONFIDENTIAL 60CONFIDENTIAL
Custom Post-logout Behavior
To add logic to dynamically determine redirect url (cont.):
• Override determineTargetUrl
protected String determineTargetUrl(...) { Authentication auth = AUTH_HOLDER.get()
String url = super.determineTargetUrl(request, response)
if (auth instanceof OrganizationAuthentication) { OrganizationAuthentication authentication = auth url = ... }
url}
61CONFIDENTIAL 61CONFIDENTIAL
Custom Post-logout Behavior
To add logic to dynamically determine redirect url (cont.):
• Redefine the logoutSuccessHandler bean in resources.groovy
import com.mycompany.myapp.CustomLogoutSuccessHandlerimport o.c.g.g.plugins.springsecurity.SpringSecurityUtils
beans = {
def conf = SpringSecurityUtils.securityConfig
logoutSuccessHandler(CustomLogoutSuccessHandler) { redirectStrategy = ref('redirectStrategy') defaultTargetUrl = conf.logout.afterLogoutUrl }}
62CONFIDENTIAL 62CONFIDENTIAL
Customization Overview
63CONFIDENTIAL 63CONFIDENTIAL
Customization Overview
Subclass or replace filters in the chain:
• SecurityContextPersistenceFilter
• MutableLogoutFilter
• RequestHolderAuthenticationFilter
(UsernamePasswordAuthenticationFilter)
• SecurityContextHolderAwareRequestFilter
• RememberMeAuthenticationFilter
• AnonymousAuthenticationFilter
• ExceptionTranslationFilter
• FilterSecurityInterceptor
64CONFIDENTIAL 64CONFIDENTIAL
Customization Overview (cont)
Remove filter(s) from the chain
• Not recommended
Add filter(s) to the chain
Add/remove/replace LogoutHandler(s)
Add/remove/replace AccessDecisionVoter(s)
Add/remove/replace AuthenticationProvider(s)
65CONFIDENTIAL 65CONFIDENTIAL
Customization Overview (cont)
Customize a filter or AuthenticationProvider's dependency
• e.g. custom UserDetailsService
Don't write code if you can customize properties or BootStrap.groovy
Read the Spring Security documentation
• Print the PDF and read it on your commute
Ask for help on the Grails User mailing list
66CONFIDENTIAL 66CONFIDENTIAL
See http://burtbeckwith.com/blog/?p=1090 for a blog post on the previous talk in London
67CONFIDENTIAL 67CONFIDENTIAL
Thank you
top related