This is an automated email from the ASF dual-hosted git repository.
rlevas pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push:
new b6a3341 [AMBARI-24985] Handle requests from a configured trusted
proxy to identify a proxied user using Kerberos
b6a3341 is described below
commit b6a33413e518a20e186609996b34671d33573c51
Author: Robert Levas <[email protected]>
AuthorDate: Mon Dec 3 12:17:10 2018 -0500
[AMBARI-24985] Handle requests from a configured trusted proxy to identify
a proxied user using Kerberos
* [AMBARI-24985] Handle requests from a configured trusted proxy to
identify a proxied user using Kerberos
* [AMBARI-24985] Handle requests from a configured trusted proxy to
identify a proxied user using Kerberos
---
.../ambari/server/api/predicate/QueryLexer.java | 2 +
.../configuration/spring/ApiSecurityConfig.java | 24 +-
.../AmbariBasicAuthenticationFilter.java | 28 +-
.../AmbariLocalAuthenticationProvider.java | 2 +-
.../AmbariProxiedUserDetailsImpl.java | 96 +++++++
.../authentication/AmbariProxyUserDetails.java | 41 +++
.../authentication/AmbariProxyUserDetailsImpl.java | 50 ++++
.../security/authentication/AmbariUserDetails.java | 67 +----
...UserDetails.java => AmbariUserDetailsImpl.java} | 10 +-
.../jwt/AmbariJwtAuthenticationProvider.java | 3 +-
.../AmbariAuthToLocalUserDetailsService.java | 50 ++--
.../AmbariKerberosAuthenticationFilter.java | 14 +-
.../AmbariKerberosAuthenticationProvider.java | 126 ++++++++
.../kerberos/AmbariProxiedUserDetailsService.java | 320 +++++++++++++++++++++
.../AmbariProxyUserKerberosDetailsImpl.java | 40 +++
.../pam/AmbariPamAuthenticationProvider.java | 3 +-
.../tproxy/AmbariTProxyConfiguration.java | 2 +-
.../tproxy/TrustedProxyAuthenticationDetails.java | 102 +++++++
.../TrustedProxyAuthenticationDetailsSource.java | 36 +++
...stedProxyAuthenticationNotAllowedException.java | 32 +++
.../AmbariLdapAuthenticationProvider.java | 3 +-
.../AmbariUserAuthorizationFilter.java | 3 +-
.../apache/ambari/server/utils/RequestUtils.java | 122 ++++++++
.../server/security/SecurityHelperImplTest.java | 4 +-
.../server/security/TestAuthenticationFactory.java | 4 +-
.../AmbariKerberosAuthenticationFilterTest.java | 8 +
.../AmbariProxiedUserDetailsServiceTest.java | 204 +++++++++++++
.../authorization/AuthorizationHelperTest.java | 4 +-
.../ambari/server/utils/RequestUtilsTest.java | 172 ++++++++++-
29 files changed, 1443 insertions(+), 129 deletions(-)
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
b/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
index b72bc48..ba42398 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/api/predicate/QueryLexer.java
@@ -53,6 +53,7 @@ public class QueryLexer {
public static final String QUERY_FROM = "from";
public static final String QUERY_MINIMAL = "minimal_response";
public static final String QUERY_SORT = "sortBy";
+ public static final String QUERY_DOAS = "doAs";
/**
* All valid deliminators.
@@ -216,6 +217,7 @@ public class QueryLexer {
SET_IGNORE.add(QUERY_FROM);
SET_IGNORE.add(QUERY_MINIMAL);
SET_IGNORE.add(QUERY_SORT);
+ SET_IGNORE.add(QUERY_DOAS);
SET_IGNORE.add("_");
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/configuration/spring/ApiSecurityConfig.java
b/ambari-server/src/main/java/org/apache/ambari/server/configuration/spring/ApiSecurityConfig.java
index 9401d1a..c551e5e 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/configuration/spring/ApiSecurityConfig.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/configuration/spring/ApiSecurityConfig.java
@@ -22,7 +22,9 @@ import
org.apache.ambari.server.security.authentication.AmbariDelegatingAuthenti
import
org.apache.ambari.server.security.authentication.AmbariLocalAuthenticationProvider;
import
org.apache.ambari.server.security.authentication.jwt.AmbariJwtAuthenticationProvider;
import
org.apache.ambari.server.security.authentication.kerberos.AmbariAuthToLocalUserDetailsService;
+import
org.apache.ambari.server.security.authentication.kerberos.AmbariKerberosAuthenticationProvider;
import
org.apache.ambari.server.security.authentication.kerberos.AmbariKerberosTicketValidator;
+import
org.apache.ambari.server.security.authentication.kerberos.AmbariProxiedUserDetailsService;
import
org.apache.ambari.server.security.authentication.pam.AmbariPamAuthenticationProvider;
import
org.apache.ambari.server.security.authorization.AmbariAuthorizationFilter;
import
org.apache.ambari.server.security.authorization.AmbariLdapAuthenticationProvider;
@@ -37,7 +39,6 @@ import
org.springframework.security.config.annotation.authentication.builders.Au
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.kerberos.authentication.KerberosServiceAuthenticationProvider;
import
org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import
org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@@ -67,15 +68,14 @@ public class ApiSecurityConfig extends
WebSecurityConfigurerAdapter{
AmbariLocalAuthenticationProvider
ambariLocalAuthenticationProvider,
AmbariLdapAuthenticationProvider
ambariLdapAuthenticationProvider,
AmbariInternalAuthenticationProvider ambariInternalAuthenticationProvider,
-
KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider
+
AmbariKerberosAuthenticationProvider ambariKerberosAuthenticationProvider
) {
auth.authenticationProvider(ambariJwtAuthenticationProvider)
.authenticationProvider(ambariPamAuthenticationProvider)
.authenticationProvider(ambariLocalAuthenticationProvider)
.authenticationProvider(ambariLdapAuthenticationProvider)
.authenticationProvider(ambariInternalAuthenticationProvider)
- .authenticationProvider(kerberosServiceAuthenticationProvider);
-
+ .authenticationProvider(ambariKerberosAuthenticationProvider);
}
@Override
@@ -98,17 +98,13 @@ public class ApiSecurityConfig extends
WebSecurityConfigurerAdapter{
}
@Bean
- public KerberosServiceAuthenticationProvider
kerberosServiceAuthenticationProvider(
+ public AmbariKerberosAuthenticationProvider
ambariKerberosAuthenticationProvider(
AmbariKerberosTicketValidator ambariKerberosTicketValidator,
- AmbariAuthToLocalUserDetailsService userDetailsService) {
-
- KerberosServiceAuthenticationProvider
kerberosServiceAuthenticationProvider =
- new KerberosServiceAuthenticationProvider();
+ AmbariAuthToLocalUserDetailsService authToLocalUserDetailsService,
+ AmbariProxiedUserDetailsService proxiedUserDetailsService) {
-
kerberosServiceAuthenticationProvider.setTicketValidator(ambariKerberosTicketValidator);
-
kerberosServiceAuthenticationProvider.setUserDetailsService(userDetailsService);
-
- return kerberosServiceAuthenticationProvider;
+ return new
AmbariKerberosAuthenticationProvider(authToLocalUserDetailsService,
+ proxiedUserDetailsService,
+ ambariKerberosTicketValidator);
}
-
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilter.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilter.java
index 32ab3a8..f7936a1 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilter.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariBasicAuthenticationFilter.java
@@ -25,6 +25,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.ambari.server.security.AmbariEntryPoint;
+import org.apache.ambari.server.utils.RequestUtils;
+import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
@@ -63,7 +65,7 @@ public class AmbariBasicAuthenticationFilter extends
BasicAuthenticationFilter i
AmbariAuthenticationEventHandler
eventHandler) {
super(authenticationManager, ambariEntryPoint);
- if(eventHandler == null) {
+ if (eventHandler == null) {
throw new IllegalArgumentException("The AmbariAuthenticationEventHandler
must not be null");
}
@@ -90,8 +92,28 @@ public class AmbariBasicAuthenticationFilter extends
BasicAuthenticationFilter i
*/
@Override
public boolean shouldApply(HttpServletRequest httpServletRequest) {
+
+ if (LOG.isDebugEnabled()) {
+ RequestUtils.logRequestHeadersAndQueryParams(httpServletRequest, LOG);
+ }
+
String header = httpServletRequest.getHeader("Authorization");
- return (header != null) && header.startsWith("Basic ");
+ if ((header != null) && header.startsWith("Basic ")) {
+ // If doAs is sent as a query parameter, ignore the basic auth header.
+ // This logic is here to help deal with a potential issue when Knox is
the trusted proxy and it
+ // forwards the original request's Authorization header (for Basic Auth)
when Kerberos authentication
+ // is required.
+ String doAsQueryParameterValue =
RequestUtils.getQueryStringParameterValue(httpServletRequest, "doAs");
+ if (StringUtils.isEmpty(doAsQueryParameterValue)) {
+ return true;
+ } else {
+ LOG.warn("The 'doAs' query parameter was provided; however, the
BasicAuth header is found. " +
+ "Ignoring the BasicAuth header hoping to negotiate Kerberos
authentication.");
+ return false;
+ }
+ } else {
+ return false;
+ }
}
@Override
@@ -104,7 +126,7 @@ public class AmbariBasicAuthenticationFilter extends
BasicAuthenticationFilter i
*
* @param httpServletRequest the request
* @param httpServletResponse the response
- * @param filterChain the Spring filter chain
+ * @param filterChain the Spring filter chain
* @throws IOException
* @throws ServletException
*/
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariLocalAuthenticationProvider.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariLocalAuthenticationProvider.java
index b8958cd..f8bfd4b 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariLocalAuthenticationProvider.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariLocalAuthenticationProvider.java
@@ -93,7 +93,7 @@ public class AmbariLocalAuthenticationProvider extends
AmbariAuthenticationProvi
}
}
- AmbariUserDetails userDetails = new
AmbariUserDetails(users.getUser(userEntity), password,
users.getUserAuthorities(userEntity));
+ AmbariUserDetails userDetails = new
AmbariUserDetailsImpl(users.getUser(userEntity), password,
users.getUserAuthorities(userEntity));
return new AmbariUserAuthentication(password, userDetails, true);
}
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxiedUserDetailsImpl.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxiedUserDetailsImpl.java
new file mode 100644
index 0000000..0ec0b5a
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxiedUserDetailsImpl.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication;
+
+import java.util.Collection;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+/**
+ * AmbariProxiedUserDetailsImpl is an {@link AmbariUserDetails} implementation
that contains details
+ * about the proxied user as well as the proxy user.
+ * <p>
+ * This data can be helpful with logging information about the user performing
an action.
+ */
+public class AmbariProxiedUserDetailsImpl implements AmbariUserDetails {
+
+ /**
+ * Details about the acting user.
+ * <p>
+ * This user was specified as the doAs or proxied user during a trusted
proxy authentication attempt
+ */
+ private final UserDetails proxiedUserDetails;
+
+ /**
+ * Details about the proxy user that was authenticated.
+ * <p>
+ * This user that authenticated wth Ambari but specified a doAs or proxied
user to be used as the acting user for operations.
+ */
+ private final AmbariProxyUserDetails proxyUserDetails;
+
+ public AmbariProxiedUserDetailsImpl(UserDetails proxiedUserDetails,
AmbariProxyUserDetails proxyUserDetails) {
+ this.proxiedUserDetails = proxiedUserDetails;
+ this.proxyUserDetails = proxyUserDetails;
+ }
+
+ @Override
+ public Integer getUserId() {
+ return (proxiedUserDetails instanceof AmbariUserDetails) ?
((AmbariUserDetails) proxiedUserDetails).getUserId() : null;
+ }
+
+ @Override
+ public Collection<? extends GrantedAuthority> getAuthorities() {
+ return (proxiedUserDetails == null) ? null :
proxiedUserDetails.getAuthorities();
+ }
+
+ @Override
+ public String getPassword() {
+ return (proxiedUserDetails == null) ? null :
proxiedUserDetails.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ return (proxiedUserDetails == null) ? null :
proxiedUserDetails.getUsername();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return (proxiedUserDetails != null) &&
proxiedUserDetails.isAccountNonExpired();
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return (proxiedUserDetails != null) &&
proxiedUserDetails.isAccountNonLocked();
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return (proxiedUserDetails != null) &&
proxiedUserDetails.isCredentialsNonExpired();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return (proxiedUserDetails != null) && proxiedUserDetails.isEnabled();
+ }
+
+ public AmbariProxyUserDetails getProxyUserDetails() {
+ return proxyUserDetails;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetails.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetails.java
new file mode 100644
index 0000000..9026654
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetails.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication;
+
+import org.apache.ambari.server.security.authorization.UserAuthenticationType;
+
+/**
+ * AmbariProxyUserDetails contains information about the proxy user
authenticated during a trusted
+ * proxy authentication attempt.
+ */
+public interface AmbariProxyUserDetails {
+ /**
+ * Returns the local username of the authenticated proxy user.
+ *
+ * @return the local username of the authenticated proxy user
+ */
+ String getUsername();
+
+ /**
+ * Returns the authentication type used to peform authentication.
+ *
+ * @return a {@link UserAuthenticationType}
+ */
+ UserAuthenticationType getAuthenticationType();
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetailsImpl.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetailsImpl.java
new file mode 100644
index 0000000..8353035
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariProxyUserDetailsImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication;
+
+import org.apache.ambari.server.security.authorization.UserAuthenticationType;
+
+/**
+ * AmbariProxyUserDetailsImpl is a general implementation of a {@link
AmbariProxyUserDetails}.
+ */
+public class AmbariProxyUserDetailsImpl implements AmbariProxyUserDetails {
+ private final String username;
+ private final UserAuthenticationType authenticationType;
+
+ /**
+ * Constructor
+ *
+ * @param username the local username
+ * @param authenticationType the {@link UserAuthenticationType}
+ */
+ public AmbariProxyUserDetailsImpl(String username, UserAuthenticationType
authenticationType) {
+ this.username = username;
+ this.authenticationType = authenticationType;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public UserAuthenticationType getAuthenticationType() {
+ return authenticationType;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
index e2c40d4..8f32b25 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
@@ -17,70 +17,13 @@
*/
package org.apache.ambari.server.security.authentication;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-
-import org.apache.ambari.server.security.authorization.User;
-import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
- * AmbariUserDetails is an implementation of {@link UserDetails} that contains
information about
- * an authenticated user needed specifically by Ambari. For example, the
user's <code>userId</code>.
- * <p>
- * Ideally instances of this class are used as the value returned by {@link
org.springframework.security.core.Authentication#getPrincipal()}
+ * AmbariUserDetails implementations are extensions of {@link UserDetails}
that contain information
+ * about the authenticated user that is needed specifically by Ambari. For
example, the user's
+ * <code>userId</code>.
*/
-public class AmbariUserDetails implements UserDetails {
-
- private final User user;
- private final String password;
- private final Collection<? extends GrantedAuthority> grantedAuthorities;
-
- public AmbariUserDetails(User user, String password, Collection<? extends
GrantedAuthority> grantedAuthorities) {
- this.user = user;
- this.password = password;
- this.grantedAuthorities = (grantedAuthorities == null)
- ? Collections.emptyList()
- : Collections.unmodifiableCollection(new
ArrayList<>(grantedAuthorities));
- }
-
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- return grantedAuthorities;
- }
-
- @Override
- public String getPassword() {
- return password;
- }
-
- @Override
- public String getUsername() {
- return (user == null) ? null : user.getUserName();
- }
-
- public Integer getUserId() {
- return (user == null) ? null : user.getUserId();
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return (user != null) && user.isActive();
- }
+public interface AmbariUserDetails extends UserDetails {
+ Integer getUserId();
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetailsImpl.java
similarity index 84%
copy from
ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
copy to
ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetailsImpl.java
index e2c40d4..3c9ea1a 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetails.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/AmbariUserDetailsImpl.java
@@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.ambari.server.security.authentication;
import java.util.ArrayList;
@@ -23,21 +24,19 @@ import java.util.Collections;
import org.apache.ambari.server.security.authorization.User;
import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
/**
- * AmbariUserDetails is an implementation of {@link UserDetails} that contains
information about
- * an authenticated user needed specifically by Ambari. For example, the
user's <code>userId</code>.
+ * AmbariUserDetails is an implementation of {@link AmbariUserDetails}
* <p>
* Ideally instances of this class are used as the value returned by {@link
org.springframework.security.core.Authentication#getPrincipal()}
*/
-public class AmbariUserDetails implements UserDetails {
+public class AmbariUserDetailsImpl implements AmbariUserDetails {
private final User user;
private final String password;
private final Collection<? extends GrantedAuthority> grantedAuthorities;
- public AmbariUserDetails(User user, String password, Collection<? extends
GrantedAuthority> grantedAuthorities) {
+ public AmbariUserDetailsImpl(User user, String password, Collection<?
extends GrantedAuthority> grantedAuthorities) {
this.user = user;
this.password = password;
this.grantedAuthorities = (grantedAuthorities == null)
@@ -60,6 +59,7 @@ public class AmbariUserDetails implements UserDetails {
return (user == null) ? null : user.getUserName();
}
+ @Override
public Integer getUserId() {
return (user == null) ? null : user.getUserId();
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/jwt/AmbariJwtAuthenticationProvider.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/jwt/AmbariJwtAuthenticationProvider.java
index 1b80fd1..076e1b7 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/jwt/AmbariJwtAuthenticationProvider.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/jwt/AmbariJwtAuthenticationProvider.java
@@ -26,6 +26,7 @@ import
org.apache.ambari.server.security.authentication.AmbariAuthenticationExce
import
org.apache.ambari.server.security.authentication.AmbariAuthenticationProvider;
import
org.apache.ambari.server.security.authentication.AmbariUserAuthentication;
import org.apache.ambari.server.security.authentication.AmbariUserDetails;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
import
org.apache.ambari.server.security.authentication.TooManyLoginFailuresException;
import org.apache.ambari.server.security.authentication.UserNotFoundException;
import org.apache.ambari.server.security.authorization.UserAuthenticationType;
@@ -118,7 +119,7 @@ public class AmbariJwtAuthenticationProvider extends
AmbariAuthenticationProvide
}
}
- AmbariUserDetails userDetails = new
AmbariUserDetails(users.getUser(userEntity), null,
users.getUserAuthorities(userEntity));
+ AmbariUserDetails userDetails = new
AmbariUserDetailsImpl(users.getUser(userEntity), null,
users.getUserAuthorities(userEntity));
return new
AmbariUserAuthentication(authentication.getCredentials().toString(),
userDetails, true);
} else {
// The user was not authenticated, fail
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariAuthToLocalUserDetailsService.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariAuthToLocalUserDetailsService.java
index 14c174a..b90a8ca 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariAuthToLocalUserDetailsService.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariAuthToLocalUserDetailsService.java
@@ -28,7 +28,7 @@ import
org.apache.ambari.server.orm.entities.UserAuthenticationEntity;
import org.apache.ambari.server.orm.entities.UserEntity;
import
org.apache.ambari.server.security.authentication.AccountDisabledException;
import
org.apache.ambari.server.security.authentication.AmbariAuthenticationException;
-import org.apache.ambari.server.security.authentication.AmbariUserDetails;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
import
org.apache.ambari.server.security.authentication.InvalidUsernamePasswordCombinationException;
import
org.apache.ambari.server.security.authentication.TooManyLoginFailuresException;
import org.apache.ambari.server.security.authentication.UserNotFoundException;
@@ -94,22 +94,7 @@ public class AmbariAuthToLocalUserDetailsService implements
UserDetailsService {
// If no entries are returned, we have not yet seen this principal. If
no, perform an auth-to-local translation
// to determine what the local username is.
if (CollectionUtils.isEmpty(entities)) {
- // Since KerberosName relies on a static variable to hold on to the
auth-to-local rules, attempt
- // to protect access to the rule set by blocking other threads from
changing the rules out from
- // under us during this operation. Similar logic is used in
org.apache.ambari.server.view.ViewContextImpl.getUsername().
- try {
- synchronized (KerberosName.class) {
- KerberosName.setRules(authToLocalRules);
- username = new KerberosName(principal).getShortName();
- }
- } catch (UserNotFoundException e) {
- throw new UsernameNotFoundException(e.getMessage(), e);
- } catch (IOException e) {
- String message = String.format("Failed to translate %s to a local
username during Kerberos authentication: %s", principal,
e.getLocalizedMessage());
- LOG.warn(message);
- throw new UsernameNotFoundException(message, e);
- }
-
+ username = translatePrincipalName(principal);
if (username == null) {
String message = String.format("Failed to translate %s to a local
username during Kerberos authentication.", principal);
LOG.warn(message);
@@ -203,6 +188,35 @@ public class AmbariAuthToLocalUserDetailsService
implements UserDetailsService {
}
}
- return new AmbariUserDetails(new User(userEntity), null,
users.getUserAuthorities(userEntity));
+ return new AmbariUserDetailsImpl(new User(userEntity), null,
users.getUserAuthorities(userEntity));
+ }
+
+ /**
+ * Using the auth-to-local rules stored in
<code>authentication.kerberos.auth_to_local.rules</code>
+ * in the <code>ambari.properties</code> file, translate the supplied
principal name to a local name.
+ *
+ * @param principalName the principal name to translate
+ * @return a local username
+ */
+ public String translatePrincipalName(String principalName) {
+ if (StringUtils.isNotEmpty(principalName) && principalName.contains("@")) {
+ try {
+ // Since KerberosName relies on a static variable to hold on to the
auth-to-local rules,
+ // attempt to protect access to the rule set by blocking other threads
from changing the
+ // rules out from under us during this operation.
+ synchronized (KerberosName.class) {
+ KerberosName.setRules(authToLocalRules);
+ return new KerberosName(principalName).getShortName();
+ }
+ } catch (UserNotFoundException e) {
+ throw new UsernameNotFoundException(e.getMessage(), e);
+ } catch (IOException e) {
+ String message = String.format("Failed to translate %s to a local
username during Kerberos authentication: %s", principalName,
e.getLocalizedMessage());
+ LOG.warn(message);
+ throw new UsernameNotFoundException(message, e);
+ }
+ } else {
+ return principalName;
+ }
}
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilter.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilter.java
index 3d77abb..bcc65eb 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilter.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilter.java
@@ -32,6 +32,10 @@ import org.apache.ambari.server.configuration.Configuration;
import
org.apache.ambari.server.security.authentication.AmbariAuthenticationEventHandler;
import
org.apache.ambari.server.security.authentication.AmbariAuthenticationException;
import
org.apache.ambari.server.security.authentication.AmbariAuthenticationFilter;
+import
org.apache.ambari.server.security.authentication.tproxy.TrustedProxyAuthenticationDetailsSource;
+import org.apache.ambari.server.utils.RequestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
@@ -52,6 +56,8 @@ import org.springframework.stereotype.Component;
@Order(2)
public class AmbariKerberosAuthenticationFilter extends
SpnegoAuthenticationProcessingFilter implements AmbariAuthenticationFilter {
+ private static final Logger LOG =
LoggerFactory.getLogger(AmbariKerberosAuthenticationFilter.class);
+
/**
* Ambari authentication event handler
*/
@@ -83,7 +89,7 @@ public class AmbariKerberosAuthenticationFilter extends
SpnegoAuthenticationProc
kerberosAuthenticationEnabled = (kerberosAuthenticationProperties != null)
&& kerberosAuthenticationProperties.isKerberosAuthenticationEnabled();
- if(eventHandler == null) {
+ if (eventHandler == null) {
throw new IllegalArgumentException("The AmbariAuthenticationEventHandler
must not be null");
}
@@ -91,6 +97,8 @@ public class AmbariKerberosAuthenticationFilter extends
SpnegoAuthenticationProc
setAuthenticationManager(authenticationManager);
+ setAuthenticationDetailsSource(new
TrustedProxyAuthenticationDetailsSource());
+
setFailureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest
httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
@@ -131,6 +139,10 @@ public class AmbariKerberosAuthenticationFilter extends
SpnegoAuthenticationProc
*/
@Override
public boolean shouldApply(HttpServletRequest httpServletRequest) {
+ if (LOG.isDebugEnabled()) {
+ RequestUtils.logRequestHeadersAndQueryParams(httpServletRequest, LOG);
+ }
+
if (kerberosAuthenticationEnabled) {
String header = httpServletRequest.getHeader("Authorization");
return (header != null) && (header.startsWith("Negotiate ") ||
header.startsWith("Kerberos "));
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationProvider.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationProvider.java
new file mode 100644
index 0000000..bf5566e
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationProvider.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication.kerberos;
+
+import javax.inject.Inject;
+
+import
org.apache.ambari.server.security.authentication.AmbariProxiedUserDetailsImpl;
+import
org.apache.ambari.server.security.authentication.tproxy.TrustedProxyAuthenticationDetails;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import
org.springframework.security.kerberos.authentication.KerberosServiceRequestToken;
+import
org.springframework.security.kerberos.authentication.KerberosTicketValidation;
+import
org.springframework.security.kerberos.authentication.KerberosTicketValidator;
+
+/**
+ * AmbariKerberosAuthenticationProvider is an {@link
org.springframework.security.authentication.AuthenticationProvider}
+ * implementation used to authenticate users using a Kerberos ticket.
+ * <p>
+ * In order for this provider to be usable, Kerberos authentication must be
enabled. This may be done
+ * using the <code>ambari-server setup-kerberos</code> CLI.
+ * <p>
+ * This implementation supports Trusted Proxy authentication.
+ */
+public class AmbariKerberosAuthenticationProvider implements
AuthenticationProvider, InitializingBean {
+ private static final Logger LOG =
LoggerFactory.getLogger(AmbariKerberosAuthenticationProvider.class);
+
+ private AmbariAuthToLocalUserDetailsService authToLocalUserDetailsService;
+ private AmbariProxiedUserDetailsService proxiedUserDetailsService;
+ private KerberosTicketValidator ticketValidator;
+
+ @Inject
+ public
AmbariKerberosAuthenticationProvider(AmbariAuthToLocalUserDetailsService
authToLocalUserDetailsService,
+ AmbariProxiedUserDetailsService
proxiedUserDetailsService,
+ KerberosTicketValidator
ticketValidator) {
+ this.authToLocalUserDetailsService = authToLocalUserDetailsService;
+ this.proxiedUserDetailsService = proxiedUserDetailsService;
+ this.ticketValidator = ticketValidator;
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws
AuthenticationException {
+
+ if (authentication == null) {
+ throw new BadCredentialsException("Missing credentials");
+ } else if (authentication instanceof KerberosServiceRequestToken) {
+ KerberosServiceRequestToken auth = (KerberosServiceRequestToken)
authentication;
+ byte[] token = auth.getToken();
+
+ LOG.debug("Validating Kerberos token");
+ KerberosTicketValidation ticketValidation =
ticketValidator.validateTicket(token);
+ LOG.debug("Kerberos token validated: {}", ticketValidation.username());
+
+ Object requestDetails = authentication.getDetails();
+ UserDetails userDetails;
+
+ if (requestDetails instanceof TrustedProxyAuthenticationDetails) {
+ TrustedProxyAuthenticationDetails trustedProxyAuthenticationDetails =
(TrustedProxyAuthenticationDetails) requestDetails;
+ String proxiedUserName = trustedProxyAuthenticationDetails.getDoAs();
+
+ if (StringUtils.isNotEmpty(proxiedUserName)) {
+ String localProxyUserName =
authToLocalUserDetailsService.translatePrincipalName(ticketValidation.username());
+
+ userDetails = new AmbariProxiedUserDetailsImpl(
+ proxiedUserDetailsService.loadProxiedUser(proxiedUserName,
localProxyUserName, trustedProxyAuthenticationDetails),
+ new
AmbariProxyUserKerberosDetailsImpl(ticketValidation.username(),
localProxyUserName));
+ } else {
+ // This is not a trusted proxy request... handle normally
+ userDetails =
authToLocalUserDetailsService.loadUserByUsername(ticketValidation.username());
+ }
+ } else {
+ // This is not a trusted proxy request... handle normally
+ userDetails =
authToLocalUserDetailsService.loadUserByUsername(ticketValidation.username());
+ }
+
+ KerberosServiceRequestToken responseAuth = new
KerberosServiceRequestToken(userDetails, ticketValidation,
userDetails.getAuthorities(), token);
+ responseAuth.setDetails(requestDetails);
+ return responseAuth;
+ } else {
+ throw new BadCredentialsException(String.format("Unexpected
Authentication class: %s", authentication.getClass().getName()));
+ }
+ }
+
+ @Override
+ public boolean supports(Class<? extends Object> auth) {
+ return KerberosServiceRequestToken.class.isAssignableFrom(auth);
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ }
+
+ public void
setAuthToLocalUserDetailsService(AmbariAuthToLocalUserDetailsService
authToLocalUserDetailsService) {
+ this.authToLocalUserDetailsService = authToLocalUserDetailsService;
+ }
+
+ public void setProxiedUserDetailsService(AmbariProxiedUserDetailsService
proxiedUserDetailsService) {
+ this.proxiedUserDetailsService = proxiedUserDetailsService;
+ }
+
+ public void setTicketValidator(KerberosTicketValidator ticketValidator) {
+ this.ticketValidator = ticketValidator;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsService.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsService.java
new file mode 100644
index 0000000..0d11a3c
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsService.java
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication.kerberos;
+
+import static java.util.stream.Collectors.toSet;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.inject.Inject;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.orm.entities.GroupEntity;
+import org.apache.ambari.server.orm.entities.MemberEntity;
+import org.apache.ambari.server.orm.entities.UserEntity;
+import
org.apache.ambari.server.security.authentication.AccountDisabledException;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
+import
org.apache.ambari.server.security.authentication.InvalidUsernamePasswordCombinationException;
+import
org.apache.ambari.server.security.authentication.TooManyLoginFailuresException;
+import
org.apache.ambari.server.security.authentication.tproxy.AmbariTProxyConfiguration;
+import
org.apache.ambari.server.security.authentication.tproxy.TrustedProxyAuthenticationDetails;
+import
org.apache.ambari.server.security.authentication.tproxy.TrustedProxyAuthenticationNotAllowedException;
+import org.apache.ambari.server.security.authorization.User;
+import org.apache.ambari.server.security.authorization.Users;
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.security.authentication.util.KerberosName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+
+import com.google.inject.Provider;
+
+/**
+ * AmbariProxiedUserDetailsService is a {@link UserDetailsService} that
handles proxied users via the
+ * Trusted Proxy protocol.
+ * <p>
+ * If trusted proxy support is enabled and a proxied user is declared, the
proxy user and remote host is validated.
+ * If all criteria is met and a user account with the the proxied user's
username exist, that user
+ * account will be set as the authenticated user.
+ */
+@Component
+public class AmbariProxiedUserDetailsService implements UserDetailsService {
+ private static final Logger LOG =
LoggerFactory.getLogger(AmbariProxiedUserDetailsService.class);
+
+ private static final Pattern IP_ADDRESS_PATTERN =
Pattern.compile("^(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$");
+ private static final Pattern IP_ADDRESS_RANGE_PATTERN =
Pattern.compile("^((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})/(\\d{1,2})$");
+
+ @Inject
+ private Provider<AmbariTProxyConfiguration>
ambariTProxyConfigurationProvider;
+
+ private final Configuration configuration;
+
+ private final Users users;
+
+ /**
+ * Constructor.
+ * <p>
+ * Given the Ambari {@link Configuration}, initializes the {@link
KerberosName} class using
+ * the <code>auth-to-local</code> rules from {@link
AmbariKerberosAuthenticationProperties#getAuthToLocalRules()}.
+ *
+ * @param configuration the Ambari configuration data
+ * @param users the Ambari users access object
+ */
+ AmbariProxiedUserDetailsService(Configuration configuration, Users users) {
+ this.configuration = configuration;
+ this.users = users;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String proxiedUserName) throws
UsernameNotFoundException {
+ return loadProxiedUser(proxiedUserName, null, null);
+ }
+
+ public UserDetails loadProxiedUser(String proxiedUserName,
+ String proxyUserName,
+ TrustedProxyAuthenticationDetails
trustedProxyAuthenticationDetails)
+ throws AuthenticationException {
+
+ LOG.info("Proxy user {} specified {} as proxied user.", proxyUserName,
proxiedUserName);
+
+ if (StringUtils.isEmpty(proxiedUserName)) {
+ String message = "No proxied username was specified.";
+ LOG.warn(message);
+ throw new UsernameNotFoundException(message);
+ }
+
+ if (trustedProxyAuthenticationDetails == null) {
+ String message = "Trusted proxy details have not been provided.";
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ AmbariTProxyConfiguration tProxyConfiguration =
ambariTProxyConfigurationProvider.get();
+
+ // Make sure the trusted proxy support is enabled
+ if (!tProxyConfiguration.isEnabled()) {
+ String message = "Trusted proxy support is not enabled.";
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ // Validate the host...
+ if (!validateHost(tProxyConfiguration, proxyUserName,
trustedProxyAuthenticationDetails.getRemoteAddress())) {
+ String message = String.format("Trusted proxy is not allowed for %s ->
%s: host match not found.", proxyUserName, proxiedUserName);
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ // Validate the user...
+ if (!validateUser(tProxyConfiguration, proxyUserName, proxiedUserName)) {
+ String message = String.format("Trusted proxy is not allowed for %s ->
%s: user match not found.", proxyUserName, proxiedUserName);
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ // Retrieve the userEntity so we can validate the groups now....
+ // We also need this later if all validations succeed.
+ UserEntity userEntity = users.getUserEntity(proxiedUserName);
+ if (userEntity == null) {
+ String message = String.format("Failed to find an account for the
proxied user, %s.", proxiedUserName);
+ LOG.warn(message);
+ throw new UsernameNotFoundException(message);
+ }
+
+ // Validate the proxied user's groups
+ if (!validateGroup(tProxyConfiguration, proxyUserName, userEntity)) {
+ String message = String.format("Trusted proxy is not allowed for %s ->
%s: group match not found.", proxyUserName, proxiedUserName);
+ LOG.warn(message);
+ throw new TrustedProxyAuthenticationNotAllowedException(message);
+ }
+
+ return createUserDetails(userEntity);
+ }
+
+ boolean validateGroup(AmbariTProxyConfiguration tProxyConfiguration, String
proxyUserName, UserEntity userEntity) {
+ String allowedGroups = tProxyConfiguration.getAllowedGroups(proxyUserName);
+
+ if (StringUtils.isNotEmpty(allowedGroups)) {
+ Set<String> groupSpecs = Arrays.stream(allowedGroups.split("\\s*,\\s*"))
+ .map(s -> s.trim().toLowerCase())
+ .collect(toSet());
+
+ if (groupSpecs.contains("*")) {
+ return true;
+ } else {
+ Set<MemberEntity> memberEntities = userEntity.getMemberEntities();
+ if (memberEntities != null) {
+ for (MemberEntity memberEntity : memberEntities) {
+ GroupEntity group = memberEntity.getGroup();
+ if (group != null) {
+ String groupName = group.getGroupName();
+ if (StringUtils.isNotEmpty(groupName) &&
groupSpecs.contains(groupName.toLowerCase())) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean validateUser(AmbariTProxyConfiguration tProxyConfiguration, String
proxyUserName, String proxiedUserName) {
+ String allowedUsers = tProxyConfiguration.getAllowedUsers(proxyUserName);
+
+ if (StringUtils.isNotEmpty(allowedUsers)) {
+ String[] userSpecs = allowedUsers.split("\\s*,\\s*");
+ for (String userSpec : userSpecs) {
+ if ("*".equals(userSpec) ||
(userSpec.equalsIgnoreCase(proxiedUserName))) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean validateHost(AmbariTProxyConfiguration tProxyConfiguration, String
proxyUserName, String remoteAddress) {
+ String allowedHosts = tProxyConfiguration.getAllowedHosts(proxyUserName);
+
+ if (StringUtils.isNotEmpty(allowedHosts)) {
+ Set<String> hostSpecs = Arrays.stream(allowedHosts.split("\\s*,\\s*"))
+ .map(s -> s.trim().toLowerCase())
+ .collect(toSet());
+
+ if (hostSpecs.contains("*")) {
+ return true;
+ } else {
+ for (String hostSpec : hostSpecs) {
+ if (isIPAddress(hostSpec)) {
+ if (hostSpec.equals(remoteAddress)) {
+ return true;
+ }
+ } else if (isIPAddressRange(hostSpec)) {
+ if (isInIpAddressRange(hostSpec, remoteAddress)) {
+ return true;
+ }
+ } else if (matchesHostname(hostSpec, remoteAddress)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ boolean matchesHostname(String hostSpec, String remoteAddress) {
+ // We assume this is a hostname... convert it to an IP address
+ try {
+ String ipAddress = getIpAddress(hostSpec);
+ if (StringUtils.isNotEmpty(ipAddress) &&
ipAddress.equals(remoteAddress)) {
+ return true;
+ }
+ } catch (Throwable t) {
+ LOG.warn("Invalid hostname in host specification, skipping: " +
hostSpec, t);
+ }
+
+ return false;
+ }
+
+ String getIpAddress(String hostname) throws UnknownHostException {
+ InetAddress inetAddress = InetAddress.getByName(hostname);
+ return (inetAddress == null) ? null : inetAddress.getHostAddress();
+ }
+
+ /**
+ * Determines if an IP address is in the subnet specified by the CIDR value.
+ * <p>
+ * The logic for ths method comes from an example found at
+ *
https://stackoverflow.com/questions/4209760/validate-an-ip-address-with-mask.
+ * <p>
+ * A CIDR is an IP address with a mask, denoting a subnet. For example,
<code>192.168.1.0/24</code> contains all
+ * IP addresses in the range of <code>192.168.1.0</code> through
<code>192.168.1.255</code>. See https://www.ipaddressguide.com/cidr
+ * for an on-line calculator.
+ *
+ * @param cidr a CIDR
+ * @param ipAddress the IP address to test
+ * @return <code>true</code> if the IP Address is withing the range
specified by the CIDR; <code>false</code> otherwise.
+ */
+ boolean isInIpAddressRange(String cidr, String ipAddress) {
+ Matcher matcher = IP_ADDRESS_RANGE_PATTERN.matcher(cidr);
+
+ if (matcher.matches() && matcher.groupCount() == 2) {
+ try {
+ String hostSpecIPAddress = matcher.group(1);
+ String hostSpecBits = matcher.group(2);
+ int hostSpecIPAddressInt = ipAddressToInt(hostSpecIPAddress);
+ int remoteAddressInt = ipAddressToInt(ipAddress);
+
+ int mask = -1 << (32 - Integer.valueOf(hostSpecBits));
+ return ((hostSpecIPAddressInt & mask) == (remoteAddressInt & mask));
+ } catch (Throwable t) {
+ LOG.warn("Invalid CIDR in host specification, skipping: " + cidr, t);
+ }
+ }
+
+ return false;
+ }
+
+ private int ipAddressToInt(String s) throws UnknownHostException {
+ InetAddress inetAddress = InetAddress.getByName(s);
+ byte[] b = inetAddress.getAddress();
+ return ((b[0] & 0xFF) << 24) |
+ ((b[1] & 0xFF) << 16) |
+ ((b[2] & 0xFF) << 8) |
+ ((b[3] & 0xFF) << 0);
+ }
+
+ private boolean isIPAddressRange(String hostSpec) {
+ return IP_ADDRESS_RANGE_PATTERN.matcher(hostSpec).matches();
+ }
+
+ private boolean isIPAddress(String hostSpec) {
+ return IP_ADDRESS_PATTERN.matcher(hostSpec).matches();
+ }
+
+ private UserDetails createUserDetails(UserEntity userEntity) {
+ String username = userEntity.getUserName();
+
+ // Ensure the user account is allowed to log in
+ try {
+ users.validateLogin(userEntity, username);
+ } catch (AccountDisabledException | TooManyLoginFailuresException e) {
+ if (configuration.showLockedOutUserMessage()) {
+ throw e;
+ } else {
+ // Do not give away information about the existence or status of a user
+ throw new InvalidUsernamePasswordCombinationException(username, false,
e);
+ }
+ }
+
+ return new AmbariUserDetailsImpl(new User(userEntity), null,
users.getUserAuthorities(userEntity));
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxyUserKerberosDetailsImpl.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxyUserKerberosDetailsImpl.java
new file mode 100644
index 0000000..21848c1
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxyUserKerberosDetailsImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication.kerberos;
+
+import
org.apache.ambari.server.security.authentication.AmbariProxyUserDetailsImpl;
+import org.apache.ambari.server.security.authorization.UserAuthenticationType;
+
+/**
+ * AmbariProxyUserKerberosDetailsImpl is a {@link AmbariProxyUserDetailsImpl}
implementation that
+ * adds allows for the proxy user's principal name to be retrieved if the
proxy user authenticated
+ * using Kerberos.
+ */
+public class AmbariProxyUserKerberosDetailsImpl extends
AmbariProxyUserDetailsImpl {
+ private final String principalName;
+
+ public AmbariProxyUserKerberosDetailsImpl(String principalName, String
localUsername) {
+ super(localUsername, UserAuthenticationType.KERBEROS);
+ this.principalName = principalName;
+ }
+
+ public String getPrincipalName() {
+ return principalName;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/pam/AmbariPamAuthenticationProvider.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/pam/AmbariPamAuthenticationProvider.java
index d92a5ca..e9e6882 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/pam/AmbariPamAuthenticationProvider.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/pam/AmbariPamAuthenticationProvider.java
@@ -34,6 +34,7 @@ import
org.apache.ambari.server.security.authentication.AmbariAuthenticationExce
import
org.apache.ambari.server.security.authentication.AmbariAuthenticationProvider;
import
org.apache.ambari.server.security.authentication.AmbariUserAuthentication;
import org.apache.ambari.server.security.authentication.AmbariUserDetails;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
import
org.apache.ambari.server.security.authentication.InvalidUsernamePasswordCombinationException;
import
org.apache.ambari.server.security.authentication.TooManyLoginFailuresException;
import org.apache.ambari.server.security.authorization.GroupType;
@@ -163,7 +164,7 @@ public class AmbariPamAuthenticationProvider extends
AmbariAuthenticationProvide
synchronizeGroups(unixUser, userEntity);
}
- AmbariUserDetails userDetails = new
AmbariUserDetails(users.getUser(userEntity), null,
users.getUserAuthorities(userEntity));
+ AmbariUserDetails userDetails = new
AmbariUserDetailsImpl(users.getUser(userEntity), null,
users.getUserAuthorities(userEntity));
return new AmbariUserAuthentication(password, userDetails, true);
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/AmbariTProxyConfiguration.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/AmbariTProxyConfiguration.java
index daf3788..7f7e79f 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/AmbariTProxyConfiguration.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/AmbariTProxyConfiguration.java
@@ -71,7 +71,7 @@ public class AmbariTProxyConfiguration extends
AmbariServerConfiguration {
* @return <code>true</code> if trusted proxy support is enabed;
<code>false</code> otherwise
* @see AmbariServerConfigurationKey#TPROXY_AUTHENTICATION_ENABLED
*/
- boolean isEnabled() {
+ public boolean isEnabled() {
return
Boolean.valueOf(getValue(AmbariServerConfigurationKey.TPROXY_AUTHENTICATION_ENABLED,
configurationMap));
}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetails.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetails.java
new file mode 100644
index 0000000..b254e667
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetails.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication.tproxy;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ambari.server.utils.RequestUtils;
+import
org.springframework.security.web.authentication.WebAuthenticationDetails;
+
+/**
+ * TrustedProxyAuthenticationDetails is a {@link WebAuthenticationDetails}
implementation that
+ * contains information from an {@link HttpServletRequest} that can be used
for handling trusted
+ * proxy authentication requests.
+ * <p>
+ * If the request is not from a trusted proxy, the internal valiues will be
null.
+ */
+public class TrustedProxyAuthenticationDetails extends
WebAuthenticationDetails {
+ private final String doAs;
+ private final String xForwardedContext;
+ private final String xForwardedProto;
+ private final String xForwardedHost;
+ private final String xForwardedFor;
+ private final String xForwardedPort;
+ private final String xForwardedServer;
+
+ /**
+ * Constructor.
+ * <p>
+ * Given an {@link HttpServletRequest}, attempts to retrieve the following
data:
+ * <ul>
+ * <li>The proxied user from the <code>doAs</code> query parameter</li>
+ * <li>
+ * The following <code>X-Forwarded-*</code> values from the request header
information
+ * <ul>
+ * <li>X-Forwarded-Context</li>
+ * <li>X-Forwarded-Proto</li>
+ * <li>X-Forwarded-Host</li>
+ * <li>X-Forwarded-For</li>
+ * <li>X-Forwarded-Port</li>
+ * <li>X-Forwarded-Server</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @param context the {@link HttpServletRequest} to query
+ */
+ TrustedProxyAuthenticationDetails(HttpServletRequest context) {
+ super(context);
+
+ this.doAs = RequestUtils.getQueryStringParameterValue(context, "doAs");
+ this.xForwardedContext = context.getHeader("X-Forwarded-Context");
+ this.xForwardedProto = context.getHeader("X-Forwarded-Proto");
+ this.xForwardedHost = context.getHeader("X-Forwarded-Host");
+ this.xForwardedFor = context.getHeader("X-Forwarded-For");
+ this.xForwardedPort = context.getHeader("X-Forwarded-Port");
+ this.xForwardedServer = context.getHeader("X-Forwarded-Server");
+ }
+
+ public String getDoAs() {
+ return doAs;
+ }
+
+ public String getXForwardedContext() {
+ return xForwardedContext;
+ }
+
+ public String getXForwardedProto() {
+ return xForwardedProto;
+ }
+
+ public String getXForwardedHost() {
+ return xForwardedHost;
+ }
+
+ public String getXForwardedFor() {
+ return xForwardedFor;
+ }
+
+ public String getXForwardedPort() {
+ return xForwardedPort;
+ }
+
+ public String getXForwardedServer() {
+ return xForwardedServer;
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetailsSource.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetailsSource.java
new file mode 100644
index 0000000..3f0af65
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationDetailsSource.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication.tproxy;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+
+/**
+ * TrustedProxyAuthenticationDetailsSource is an implementation of a {@link
AuthenticationDetailsSource}
+ * that produces {@link TrustedProxyAuthenticationDetails} instances.
+ *
+ * @see TrustedProxyAuthenticationDetails
+ */
+public class TrustedProxyAuthenticationDetailsSource implements
AuthenticationDetailsSource<HttpServletRequest,
TrustedProxyAuthenticationDetails> {
+ @Override
+ public TrustedProxyAuthenticationDetails buildDetails(HttpServletRequest
httpServletRequest) {
+ return new TrustedProxyAuthenticationDetails(httpServletRequest);
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationNotAllowedException.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationNotAllowedException.java
new file mode 100644
index 0000000..033c18e
--- /dev/null
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authentication/tproxy/TrustedProxyAuthenticationNotAllowedException.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication.tproxy;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * TrustedProxyAuthenticationNotAllowedException is an AuthenticationException
implementation to be
+ * thrown a Trusted proxy authentication attempt fails.
+ */
+public class TrustedProxyAuthenticationNotAllowedException extends
AuthenticationException {
+
+ public TrustedProxyAuthenticationNotAllowedException(String message) {
+ super(message);
+ }
+}
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
index deabb2a..7c2f50a 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
@@ -29,6 +29,7 @@ import
org.apache.ambari.server.security.authentication.AccountDisabledException
import
org.apache.ambari.server.security.authentication.AmbariAuthenticationProvider;
import
org.apache.ambari.server.security.authentication.AmbariUserAuthentication;
import org.apache.ambari.server.security.authentication.AmbariUserDetails;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
import
org.apache.ambari.server.security.authentication.InvalidUsernamePasswordCombinationException;
import
org.apache.ambari.server.security.authentication.TooManyLoginFailuresException;
import org.apache.commons.collections.CollectionUtils;
@@ -110,7 +111,7 @@ public class AmbariLdapAuthenticationProvider extends
AmbariAuthenticationProvid
}
}
- AmbariUserDetails userDetails = new
AmbariUserDetails(users.getUser(userEntity), null,
users.getUserAuthorities(userEntity));
+ AmbariUserDetails userDetails = new
AmbariUserDetailsImpl(users.getUser(userEntity), null,
users.getUserAuthorities(userEntity));
return new AmbariUserAuthentication(null, userDetails, true);
}
} catch (AuthenticationException e) {
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariUserAuthorizationFilter.java
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariUserAuthorizationFilter.java
index f252b0e..74394e4 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariUserAuthorizationFilter.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariUserAuthorizationFilter.java
@@ -33,6 +33,7 @@ import org.apache.ambari.server.orm.entities.UserEntity;
import org.apache.ambari.server.scheduler.ExecutionScheduleManager;
import
org.apache.ambari.server.security.authentication.AmbariUserAuthentication;
import org.apache.ambari.server.security.authentication.AmbariUserDetails;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
import
org.apache.ambari.server.security.authorization.internal.InternalTokenClientFilter;
import
org.apache.ambari.server.security.authorization.internal.InternalTokenStorage;
import org.apache.commons.lang.math.NumberUtils;
@@ -83,7 +84,7 @@ public class AmbariUserAuthorizationFilter implements Filter {
httpResponse.flushBuffer();
return;
} else {
- AmbariUserDetails userDetails = new
AmbariUserDetails(users.getUser(userEntity), null,
users.getUserAuthorities(userEntity));
+ AmbariUserDetails userDetails = new
AmbariUserDetailsImpl(users.getUser(userEntity), null,
users.getUserAuthorities(userEntity));
AmbariUserAuthentication authentication = new
AmbariUserAuthentication(token, userDetails, true);
SecurityContextHolder.getContext().setAuthentication(authentication);
httpResponse.setHeader("User",
AuthorizationHelper.getAuthenticatedName());
diff --git
a/ambari-server/src/main/java/org/apache/ambari/server/utils/RequestUtils.java
b/ambari-server/src/main/java/org/apache/ambari/server/utils/RequestUtils.java
index a3f9660..d0efb97 100644
---
a/ambari-server/src/main/java/org/apache/ambari/server/utils/RequestUtils.java
+++
b/ambari-server/src/main/java/org/apache/ambari/server/utils/RequestUtils.java
@@ -18,12 +18,19 @@
package org.apache.ambari.server.utils;
import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.springframework.util.MultiValueMap;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.util.UriComponentsBuilder;
import com.google.common.collect.ImmutableSet;
@@ -96,4 +103,119 @@ public class RequestUtils {
((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest() != null;
}
+ /**
+ * Posts an informational message the the supplied {@link Logger} showing
the request headers and
+ * query parameters
+ * <p>
+ * For example:
+ * <pre>
+ * ##### HEADERS ########
+ * X-Requested-By = ambari
+ * ...
+ * Accept-Encoding = gzip, deflate
+ * Accept-Language = en-US,en;q=0.9
+ * ######################
+ * ##### PARAMETERS #####
+ * _ = 1543700737939
+ * ######################
+ * </pre>
+ *
+ * @param request the {@link HttpServletRequest} to log
+ * @param logger the {@link Logger}
+ */
+ public static void logRequestHeadersAndQueryParams(HttpServletRequest
request, Logger logger) {
+ if (logger != null) {
+ StringBuilder builder;
+
+ builder = new StringBuilder();
+ builder.append("\n##### HEADERS #######");
+ Enumeration<String> headerNames = request.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String name = headerNames.nextElement();
+ Enumeration<String> values = request.getHeaders(name);
+ while (values.hasMoreElements()) {
+ String value = values.nextElement();
+ builder.append("\n\t");
+ builder.append(name);
+ builder.append(" = ");
+ builder.append(value);
+ }
+ }
+ builder.append("\n#####################");
+ builder.append("\n##### PARAMETERS ####");
+ MultiValueMap<String, String> queryParams =
getQueryStringParameters(request);
+ if (queryParams != null) {
+ for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
+ String name = entry.getKey();
+ List<String> values = entry.getValue();
+ for (String value : values) {
+ builder.append("\n\t");
+ builder.append(name);
+ builder.append(" = ");
+ builder.append(value);
+ }
+ }
+ }
+ builder.append("\n#####################");
+
+ logger.info(builder.toString());
+ }
+ }
+
+ /**
+ * Returns a {@link MultiValueMap} of the parameters parsed from the
request's query string. The
+ * returned map will not contain any parameters that may be in the body of
the request as form data.
+ * <p>
+ * This implementation manually parses the query string rather than use
{@link HttpServletRequest#getParameterValues(String)}
+ * so that the message body remains intact and available. Calling {@link
HttpServletRequest#getParameterValues(String)}
+ * could interfere with processing the body of this request later since the
body is parsed to find
+ * any form parameters.
+ *
+ * @param request the {@link HttpServletRequest}
+ * @return a Map of query parameters to values
+ */
+ public static MultiValueMap<String, String>
getQueryStringParameters(HttpServletRequest request) {
+ // Manually parse the query string rather than use
HttpServletRequest#getParameter so that
+ // the message body remains intact and available. Calling
HttpServletRequest#getParameter
+ // could interfere with processing the body of this request later since
the body needs to be
+ // parsed to find any form parameters.
+ String queryString = request.getQueryString();
+ return (StringUtils.isEmpty(queryString))
+ ? null
+ :
UriComponentsBuilder.newInstance().query(queryString).build().getQueryParams();
+ }
+
+ /**
+ * Returns a {@link List} of values parsed from the request's query string
for the given parameter
+ * name.
+ *
+ * @param request the {@link HttpServletRequest}
+ * @return a List of values for the specified query parameter; or
<code>null</code> if the
+ * requested parameter is not present
+ * @see #getQueryStringParameters(HttpServletRequest)
+ */
+ public static List<String> getQueryStringParameterValues(HttpServletRequest
request, String parameterName) {
+ MultiValueMap<String, String> valueMap = getQueryStringParameters(request);
+ return ((valueMap == null) || !valueMap.containsKey(parameterName))
+ ? null
+ : valueMap.get(parameterName);
+ }
+
+ /**
+ * Returns the value parsed from the request's query string for the given
parameter name.
+ * <p>
+ * If more than one value for the given parameter name is found, the first
value will be retured.
+ *
+ * @param request the {@link HttpServletRequest}
+ * @return the value for the specified query parameter; or <code>null</code>
if the
+ * requested parameter is not present
+ * @see #getQueryStringParameters(HttpServletRequest)
+ */
+ public static String getQueryStringParameterValue(HttpServletRequest
request, String parameterName) {
+ MultiValueMap<String, String> valueMap = getQueryStringParameters(request);
+ return ((valueMap == null) || !valueMap.containsKey(parameterName))
+ ? null
+ : valueMap.getFirst(parameterName);
+ }
+
}
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/security/SecurityHelperImplTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/security/SecurityHelperImplTest.java
index cbe23d0..2384dab 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/security/SecurityHelperImplTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/security/SecurityHelperImplTest.java
@@ -23,7 +23,7 @@ import java.util.Collection;
import org.apache.ambari.server.orm.entities.PrincipalEntity;
import org.apache.ambari.server.orm.entities.UserEntity;
import
org.apache.ambari.server.security.authentication.AmbariUserAuthentication;
-import org.apache.ambari.server.security.authentication.AmbariUserDetails;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
import org.apache.ambari.server.security.authorization.User;
import org.apache.ambari.server.security.authorization.UserName;
import org.junit.Assert;
@@ -48,7 +48,7 @@ public class SecurityHelperImplTest {
userEntity.setUserName(UserName.fromString("userName").toString());
userEntity.setUserId(1);
User user = new User(userEntity);
- Authentication auth = new AmbariUserAuthentication(null, new
AmbariUserDetails(user, null, null));
+ Authentication auth = new AmbariUserAuthentication(null, new
AmbariUserDetailsImpl(user, null, null));
ctx.setAuthentication(auth);
// Username is expected to be lowercase
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java
b/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java
index f43e5e5..10277f2 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/security/TestAuthenticationFactory.java
@@ -30,7 +30,7 @@ import org.apache.ambari.server.orm.entities.ResourceEntity;
import org.apache.ambari.server.orm.entities.ResourceTypeEntity;
import org.apache.ambari.server.orm.entities.UserEntity;
import
org.apache.ambari.server.security.authentication.AmbariUserAuthentication;
-import org.apache.ambari.server.security.authentication.AmbariUserDetails;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
import org.apache.ambari.server.security.authorization.AmbariGrantedAuthority;
import org.apache.ambari.server.security.authorization.ResourceType;
import org.apache.ambari.server.security.authorization.RoleAuthorization;
@@ -427,6 +427,6 @@ public class TestAuthenticationFactory {
userEntity.setUserName(username);
userEntity.setPrincipal(principal);
- return new AmbariUserAuthentication(null, new AmbariUserDetails(new
User(userEntity), null, authorities), true);
+ return new AmbariUserAuthentication(null, new AmbariUserDetailsImpl(new
User(userEntity), null, authorities), true);
}
}
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilterTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilterTest.java
index a0b7aca..f5087a1 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilterTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariKerberosAuthenticationFilterTest.java
@@ -19,12 +19,14 @@
package org.apache.ambari.server.security.authentication.kerberos;
import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.newCapture;
+import static org.easymock.EasyMock.startsWith;
import java.io.IOException;
import java.util.List;
@@ -150,8 +152,11 @@ public class AmbariKerberosAuthenticationFilterTest
extends EasyMockSupport {
FilterChain filterChain = createMock(FilterChain.class);
expect(request.getHeader("Authorization")).andReturn("Negotiate ").once();
+
expect(request.getHeader(startsWith("X-Forwarded-"))).andReturn(null).times(6);
expect(request.getRemoteAddr()).andReturn("1.2.3.4").once();
expect(request.getSession(false)).andReturn(session).once();
+ expect(request.getQueryString()).andReturn(null).once();
+ expect(request.getParameter(anyString())).andReturn(null).anyTimes();
expect(session.getId()).andReturn("sessionID").once();
expect(authenticationManager.authenticate(anyObject(Authentication.class)))
@@ -197,8 +202,11 @@ public class AmbariKerberosAuthenticationFilterTest
extends EasyMockSupport {
FilterChain filterChain = createMock(FilterChain.class);
expect(request.getHeader("Authorization")).andReturn("Negotiate ").once();
+
expect(request.getHeader(startsWith("X-Forwarded-"))).andReturn(null).times(6);
expect(request.getRemoteAddr()).andReturn("1.2.3.4").once();
expect(request.getSession(false)).andReturn(session).once();
+ expect(request.getQueryString()).andReturn(null).once();
+ expect(request.getParameter(anyString())).andReturn(null).anyTimes();
expect(session.getId()).andReturn("sessionID").once();
expect(authenticationManager.authenticate(anyObject(Authentication.class))).andThrow(new
InvalidUsernamePasswordCombinationException("user")).once();
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsServiceTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsServiceTest.java
new file mode 100644
index 0000000..ba310d4
--- /dev/null
+++
b/ambari-server/src/test/java/org/apache/ambari/server/security/authentication/kerberos/AmbariProxiedUserDetailsServiceTest.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.ambari.server.security.authentication.kerberos;
+
+import static org.easymock.EasyMock.expect;
+
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.orm.entities.GroupEntity;
+import org.apache.ambari.server.orm.entities.MemberEntity;
+import org.apache.ambari.server.orm.entities.UserEntity;
+import
org.apache.ambari.server.security.authentication.tproxy.AmbariTProxyConfiguration;
+import org.apache.ambari.server.security.authorization.Users;
+import org.easymock.EasyMockSupport;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AmbariProxiedUserDetailsServiceTest extends EasyMockSupport {
+ @Test
+ public void testValidateHost() throws UnknownHostException {
+ AmbariProxiedUserDetailsService service =
createMockBuilder(AmbariProxiedUserDetailsService.class)
+ .withConstructor(createNiceMock(Configuration.class),
createNiceMock(Users.class))
+ .addMockedMethod("getIpAddress", String.class)
+ .createMock();
+
expect(service.getIpAddress("host1.example.com")).andReturn("192.168.74.101").anyTimes();
+
expect(service.getIpAddress("host2.example.com")).andReturn("192.168.74.102").anyTimes();
+
+ AmbariTProxyConfiguration tproxyConfigration =
createMock(AmbariTProxyConfiguration.class);
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("*").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("192.168.74.101").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("host1.example.com").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("192.168.74.0/24").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn(null).once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("192.168.74.102").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("host2.example.com").once();
+
expect(tproxyConfigration.getAllowedHosts("proxyUser")).andReturn("192.168.74.1/32").once();
+
+ replayAll();
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "*"
+ Assert.assertTrue(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "192.168.74.101"
+ Assert.assertTrue(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "host1.example.com"
+ Assert.assertTrue(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "192.168.74.0/24"
+ Assert.assertTrue(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = null
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = ""
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "192.168.74.102"
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "host1.example.com"
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "192.168.74.1/32"
+ Assert.assertFalse(service.validateHost(tproxyConfigration, "proxyUser",
"192.168.74.101"));
+
+ verifyAll();
+ }
+
+ @Test
+ public void testValidateUser() {
+ AmbariProxiedUserDetailsService service = new
AmbariProxiedUserDetailsService(createNiceMock(Configuration.class),
createNiceMock(Users.class));
+
+ AmbariTProxyConfiguration tproxyConfigration =
createMock(AmbariTProxyConfiguration.class);
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("*").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("validUser").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("validuser").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("validUser,
tom, *").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn(null).once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("").once();
+
expect(tproxyConfigration.getAllowedUsers("proxyUser")).andReturn("notValidUser").once();
+
+ replayAll();
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "*"
+ Assert.assertTrue(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "validUser"
+ Assert.assertTrue(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "validuser" (different case)
+ Assert.assertTrue(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "validUser, tom, *"
+ Assert.assertTrue(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = null
+ Assert.assertFalse(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = ""
+ Assert.assertFalse(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ // ambari.tproxy.proxyuser.proxyUser.users = "notValidUser"
+ Assert.assertFalse(service.validateUser(tproxyConfigration, "proxyUser",
"validUser"));
+
+ verifyAll();
+ }
+
+ @Test
+ public void testValidateGroup() {
+ AmbariProxiedUserDetailsService service = new
AmbariProxiedUserDetailsService(createNiceMock(Configuration.class),
createNiceMock(Users.class));
+
+ AmbariTProxyConfiguration tproxyConfigration =
createMock(AmbariTProxyConfiguration.class);
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("*").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("validGroup").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("validgroup").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("validGroup,
*").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("").once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn(null).once();
+
expect(tproxyConfigration.getAllowedGroups("proxyUser")).andReturn("notValidGroup").once();
+
+ Set<MemberEntity> memberEntities = new HashSet<>();
+ memberEntities.add(createMockMemberEntity("validGroup"));
+ memberEntities.add(createMockMemberEntity("users"));
+
+ // Null Group name - maybe this is not possible
+ memberEntities.add(createMockMemberEntity(null));
+
+ // Null Group - maybe this is not possible
+ MemberEntity memberEntity = createMock(MemberEntity.class);
+ expect(memberEntity.getGroup()).andReturn(null).anyTimes();
+ memberEntities.add(memberEntity);
+
+ UserEntity userEntity = createMock(UserEntity.class);
+
expect(userEntity.getMemberEntities()).andReturn(memberEntities).anyTimes();
+
+ replayAll();
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "*"
+ Assert.assertTrue(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "validGroup"
+ Assert.assertTrue(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "validgroup" (different
case)
+ Assert.assertTrue(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "validGroup, *"
+ Assert.assertTrue(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = null
+ Assert.assertFalse(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = ""
+ Assert.assertFalse(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ // ambari.tproxy.proxyuser.proxyUser.groups = "notValidGroup"
+ Assert.assertFalse(service.validateGroup(tproxyConfigration, "proxyUser",
userEntity));
+
+ verifyAll();
+ }
+
+ @Test
+ public void testIsInIpAddressRange() {
+ AmbariProxiedUserDetailsService service = new
AmbariProxiedUserDetailsService(createNiceMock(Configuration.class),
createNiceMock(Users.class));
+
+ Assert.assertTrue(service.isInIpAddressRange("192.168.74.10/32",
"192.168.74.10"));
+ Assert.assertFalse(service.isInIpAddressRange("192.168.74.10/32",
"192.168.74.11"));
+
+ for (int i = 0; i <= 255; i++) {
+ Assert.assertTrue(service.isInIpAddressRange("192.168.1.0/24",
String.format("192.168.1.%d", i)));
+ }
+ Assert.assertFalse(service.isInIpAddressRange("192.168.1.0/24",
"192.168.2.100"));
+ }
+
+ private MemberEntity createMockMemberEntity(String groupName) {
+ GroupEntity groupEntity = createMock(GroupEntity.class);
+ expect(groupEntity.getGroupName()).andReturn(groupName).anyTimes();
+
+ MemberEntity memberEntity = createMock(MemberEntity.class);
+ expect(memberEntity.getGroup()).andReturn(groupEntity).anyTimes();
+ return memberEntity;
+ }
+
+}
\ No newline at end of file
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AuthorizationHelperTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AuthorizationHelperTest.java
index 8ecef22..598f2be 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AuthorizationHelperTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AuthorizationHelperTest.java
@@ -46,7 +46,7 @@ import
org.apache.ambari.server.orm.entities.ResourceTypeEntity;
import org.apache.ambari.server.orm.entities.RoleAuthorizationEntity;
import org.apache.ambari.server.orm.entities.UserEntity;
import
org.apache.ambari.server.security.authentication.AmbariUserAuthentication;
-import org.apache.ambari.server.security.authentication.AmbariUserDetails;
+import org.apache.ambari.server.security.authentication.AmbariUserDetailsImpl;
import org.easymock.EasyMockRule;
import org.easymock.EasyMockSupport;
import org.easymock.Mock;
@@ -170,7 +170,7 @@ public class AuthorizationHelperTest extends
EasyMockSupport {
userEntity.setUserId(1);
userEntity.setPrincipal(principalEntity);
User user = new User(userEntity);
- Authentication auth = new AmbariUserAuthentication(null, new
AmbariUserDetails(user, null, null));
+ Authentication auth = new AmbariUserAuthentication(null, new
AmbariUserDetailsImpl(user, null, null));
SecurityContextHolder.getContext().setAuthentication(auth);
userId = AuthorizationHelper.getAuthenticatedId();
diff --git
a/ambari-server/src/test/java/org/apache/ambari/server/utils/RequestUtilsTest.java
b/ambari-server/src/test/java/org/apache/ambari/server/utils/RequestUtilsTest.java
index d3dcad3..6452d99 100644
---
a/ambari-server/src/test/java/org/apache/ambari/server/utils/RequestUtilsTest.java
+++
b/ambari-server/src/test/java/org/apache/ambari/server/utils/RequestUtilsTest.java
@@ -18,20 +18,22 @@
package org.apache.ambari.server.utils;
import static org.easymock.EasyMock.anyString;
-import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
+import java.util.List;
+
import javax.servlet.http.HttpServletRequest;
+import org.easymock.EasyMockSupport;
+import org.junit.Assert;
import org.junit.Test;
+import org.springframework.util.MultiValueMap;
-public class RequestUtilsTest {
+public class RequestUtilsTest extends EasyMockSupport {
- public static final String REMOTE_ADDRESS = "12.13.14.15";
- public static final String REMOTE_ADDRESS_MULTIPLE =
"12.13.14.15,12.13.14.16";
+ private static final String REMOTE_ADDRESS = "12.13.14.15";
+ private static final String REMOTE_ADDRESS_MULTIPLE =
"12.13.14.15,12.13.14.16";
@Test
public void testGetRemoteAddress() {
@@ -42,12 +44,12 @@ public class RequestUtilsTest {
expect(mockedRequest.getHeader("WL-Proxy-Client-IP")).andReturn("");
expect(mockedRequest.getHeader("HTTP_CLIENT_IP")).andReturn("unknown");
expect(mockedRequest.getHeader("HTTP_X_FORWARDED_FOR")).andReturn(REMOTE_ADDRESS);
- replay(mockedRequest);
+ replayAll();
// WHEN
String remoteAddress = RequestUtils.getRemoteAddress(mockedRequest);
// THEN
assertEquals(REMOTE_ADDRESS, remoteAddress);
- verify(mockedRequest);
+ verifyAll();
}
@Test
@@ -59,12 +61,12 @@ public class RequestUtilsTest {
expect(mockedRequest.getHeader("WL-Proxy-Client-IP")).andReturn("");
expect(mockedRequest.getHeader("HTTP_CLIENT_IP")).andReturn("unknown");
expect(mockedRequest.getHeader("HTTP_X_FORWARDED_FOR")).andReturn(REMOTE_ADDRESS_MULTIPLE);
- replay(mockedRequest);
+ replayAll();
// WHEN
String remoteAddress = RequestUtils.getRemoteAddress(mockedRequest);
// THEN
assertEquals(REMOTE_ADDRESS, remoteAddress);
- verify(mockedRequest);
+ verifyAll();
}
@Test
@@ -72,12 +74,12 @@ public class RequestUtilsTest {
// GIVEN
HttpServletRequest mockedRequest = createMock(HttpServletRequest.class);
expect(mockedRequest.getHeader("X-Forwarded-For")).andReturn(REMOTE_ADDRESS);
- replay(mockedRequest);
+ replayAll();
// WHEN
String remoteAddress = RequestUtils.getRemoteAddress(mockedRequest);
// THEN
assertEquals(REMOTE_ADDRESS, remoteAddress);
- verify(mockedRequest);
+ verifyAll();
}
@Test
@@ -86,11 +88,153 @@ public class RequestUtilsTest {
HttpServletRequest mockedRequest = createMock(HttpServletRequest.class);
expect(mockedRequest.getHeader(anyString())).andReturn(null).times(5);
expect(mockedRequest.getRemoteAddr()).andReturn(REMOTE_ADDRESS);
- replay(mockedRequest);
+ replayAll();
// WHEN
String remoteAddress = RequestUtils.getRemoteAddress(mockedRequest);
// THEN
assertEquals(REMOTE_ADDRESS, remoteAddress);
- verify(mockedRequest);
+ verifyAll();
+ }
+
+
+ @Test
+ public void testGetQueryStringParameters() {
+
+ HttpServletRequest request = createMock(HttpServletRequest.class);
+ // Null query string
+ expect(request.getQueryString()).andReturn(null).once();
+ // Empty query string
+ expect(request.getQueryString()).andReturn("").once();
+ // Single parameter query string
+ expect(request.getQueryString()).andReturn("p1=1").once();
+ // Multiple parameter query string
+ expect(request.getQueryString()).andReturn("p1=1&p2=2").once();
+ // Multiple parameter query string, one with multiple values
+ expect(request.getQueryString()).andReturn("p1=1a&p2=2&p1=1b").once();
+
+ replayAll();
+ // Null query string
+ Assert.assertNull(RequestUtils.getQueryStringParameters(request));
+
+ // Empty query string
+ Assert.assertNull(RequestUtils.getQueryStringParameters(request));
+
+ MultiValueMap<String, String> parameters;
+
+ // Single parameter query string
+ parameters = RequestUtils.getQueryStringParameters(request);
+ Assert.assertNotNull(parameters);
+ Assert.assertEquals(1, parameters.size());
+ Assert.assertNotNull(parameters.get("p1"));
+ Assert.assertEquals(1, parameters.get("p1").size());
+ Assert.assertEquals("1", parameters.get("p1").get(0));
+
+ // Multiple parameter query string
+ parameters = RequestUtils.getQueryStringParameters(request);
+ Assert.assertNotNull(parameters);
+ Assert.assertEquals(2, parameters.size());
+ Assert.assertNotNull(parameters.get("p1"));
+ Assert.assertEquals(1, parameters.get("p1").size());
+ Assert.assertEquals("1", parameters.get("p1").get(0));
+ Assert.assertNotNull(parameters.get("p2"));
+ Assert.assertEquals(1, parameters.get("p2").size());
+ Assert.assertEquals("2", parameters.get("p2").get(0));
+
+ // Multiple parameter query string, one with multiple values
+ parameters = RequestUtils.getQueryStringParameters(request);
+ Assert.assertNotNull(parameters);
+ Assert.assertEquals(2, parameters.size());
+ Assert.assertNotNull(parameters.get("p1"));
+ Assert.assertEquals(2, parameters.get("p1").size());
+ Assert.assertEquals("1a", parameters.get("p1").get(0));
+ Assert.assertEquals("1b", parameters.get("p1").get(1));
+ Assert.assertNotNull(parameters.get("p2"));
+ Assert.assertEquals(1, parameters.get("p2").size());
+ Assert.assertEquals("2", parameters.get("p2").get(0));
+
+ verifyAll();
+ }
+
+ @Test
+ public void testGetQueryStringParameterValues() {
+ HttpServletRequest request = createMock(HttpServletRequest.class);
+ // Null query string
+ expect(request.getQueryString()).andReturn(null).once();
+ // Empty query string
+ expect(request.getQueryString()).andReturn("").once();
+ // Single parameter query string
+ expect(request.getQueryString()).andReturn("p1=1").once();
+ // Multiple parameter query string
+ expect(request.getQueryString()).andReturn("p1=1&p2=2").once();
+ // Multiple parameter query string, one with multiple values
+ expect(request.getQueryString()).andReturn("p1=1a&p2=2&p1=1b").once();
+
+ replayAll();
+ // Null query string
+ Assert.assertNull(RequestUtils.getQueryStringParameterValues(request,
"p1"));
+
+ // Empty query string
+ Assert.assertNull(RequestUtils.getQueryStringParameterValues(request,
"p1"));
+
+ List<String> parameterValues;
+
+ // Single parameter query string
+ parameterValues = RequestUtils.getQueryStringParameterValues(request,
"p1");
+ Assert.assertNotNull(parameterValues);
+ Assert.assertEquals(1, parameterValues.size());
+ Assert.assertEquals("1", parameterValues.get(0));
+
+ // Multiple parameter query string
+ parameterValues = RequestUtils.getQueryStringParameterValues(request,
"p2");
+ Assert.assertNotNull(parameterValues);
+ Assert.assertEquals(1, parameterValues.size());
+ Assert.assertEquals("2", parameterValues.get(0));
+
+ // Multiple parameter query string, one with multiple values
+ parameterValues = RequestUtils.getQueryStringParameterValues(request,
"p1");
+ Assert.assertNotNull(parameterValues);
+ Assert.assertEquals(2, parameterValues.size());
+ Assert.assertEquals("1a", parameterValues.get(0));
+ Assert.assertEquals("1b", parameterValues.get(1));
+
+ verifyAll();
+ }
+
+ @Test
+ public void testGetQueryStringParameterValue() {
+ HttpServletRequest request = createMock(HttpServletRequest.class);
+ // Null query string
+ expect(request.getQueryString()).andReturn(null).once();
+ // Empty query string
+ expect(request.getQueryString()).andReturn("").once();
+ // Single parameter query string
+ expect(request.getQueryString()).andReturn("p1=1").once();
+ // Multiple parameter query string
+ expect(request.getQueryString()).andReturn("p1=1&p2=2").once();
+ // Multiple parameter query string, one with multiple values
+ expect(request.getQueryString()).andReturn("p1=1a&p2=2&p1=1b").once();
+
+ replayAll();
+ // Null query string
+ Assert.assertNull(RequestUtils.getQueryStringParameterValue(request,
"p1"));
+
+ // Empty query string
+ Assert.assertNull(RequestUtils.getQueryStringParameterValue(request,
"p1"));
+
+ String parameterValue;
+
+ // Single parameter query string
+ parameterValue = RequestUtils.getQueryStringParameterValue(request, "p1");
+ Assert.assertEquals("1", parameterValue);
+
+ // Multiple parameter query string
+ parameterValue = RequestUtils.getQueryStringParameterValue(request, "p2");
+ Assert.assertEquals("2", parameterValue);
+
+ // Multiple parameter query string, one with multiple values
+ parameterValue = RequestUtils.getQueryStringParameterValue(request, "p1");
+ Assert.assertEquals("1a", parameterValue);
+
+ verifyAll();
}
}