-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add IpFilter for restricting access to resources from those coming fr…
…om without (or outside) specific IP ranges. Add IpAddressMatcher taken from Spring Security used for range tests IpFilter fixes based on code review comments Add ip to DefaultFilter
- Loading branch information
Showing
8 changed files
with
470 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
web/src/main/java/org/apache/shiro/web/filter/authz/IpAddressMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright 2002-2016 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.apache.shiro.web.filter.authz; | ||
|
||
import java.net.InetAddress; | ||
import java.net.UnknownHostException; | ||
import java.util.Arrays; | ||
|
||
/** | ||
* Matches a request based on IP Address or subnet mask matching against the remote | ||
* address. | ||
* <p> | ||
* Both IPv6 and IPv4 addresses are supported, but a matcher which is configured with an | ||
* IPv4 address will never match a request which returns an IPv6 address, and vice-versa. | ||
* | ||
* @see <a href="https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java">Original Spring Security version</a> | ||
* @since 1.5 | ||
*/ | ||
public final class IpAddressMatcher { | ||
private final int nMaskBits; | ||
private final InetAddress requiredAddress; | ||
|
||
/** | ||
* Takes a specific IP address or a range specified using the IP/Netmask (e.g. | ||
* 192.168.1.0/24 or 202.24.0.0/14). | ||
* | ||
* @param ipAddress the address or range of addresses from which the request must | ||
* come. | ||
*/ | ||
public IpAddressMatcher(String ipAddress) { | ||
int i = ipAddress.indexOf('/'); | ||
if (i > 0) { | ||
nMaskBits = Integer.parseInt(ipAddress.substring(i + 1)); | ||
ipAddress = ipAddress.substring(0, i); | ||
} else { | ||
nMaskBits = -1; | ||
} | ||
requiredAddress = parseAddress(ipAddress); | ||
} | ||
|
||
public boolean matches(String address) { | ||
InetAddress remoteAddress = parseAddress(address); | ||
|
||
if (!requiredAddress.getClass().equals(remoteAddress.getClass())) { | ||
return false; | ||
} | ||
|
||
if (nMaskBits < 0) { | ||
return remoteAddress.equals(requiredAddress); | ||
} | ||
|
||
byte[] remAddr = remoteAddress.getAddress(); | ||
byte[] reqAddr = requiredAddress.getAddress(); | ||
|
||
int oddBits = nMaskBits % 8; | ||
int nMaskBytes = nMaskBits / 8 + (oddBits == 0 ? 0 : 1); | ||
byte[] mask = new byte[nMaskBytes]; | ||
|
||
Arrays.fill(mask, 0, oddBits == 0 ? mask.length : mask.length - 1, (byte) 0xFF); | ||
|
||
if (oddBits != 0) { | ||
int finalByte = (1 << oddBits) - 1; | ||
finalByte <<= 8 - oddBits; | ||
mask[mask.length - 1] = (byte) finalByte; | ||
} | ||
|
||
for (int i = 0; i < mask.length; i++) { | ||
if ((remAddr[i] & mask[i]) != (reqAddr[i] & mask[i])) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private InetAddress parseAddress(String address) { | ||
try { | ||
return InetAddress.getByName(address); | ||
} | ||
catch (UnknownHostException e) { | ||
throw new IllegalArgumentException("Failed to parse address" + address, e); | ||
} | ||
} | ||
} |
142 changes: 142 additions & 0 deletions
142
web/src/main/java/org/apache/shiro/web/filter/authz/IpFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* | ||
* 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.shiro.web.filter.authz; | ||
|
||
import org.apache.shiro.config.ConfigurationException; | ||
import org.apache.shiro.util.StringUtils; | ||
import org.apache.shiro.web.util.WebUtils; | ||
|
||
import javax.servlet.ServletRequest; | ||
import javax.servlet.ServletResponse; | ||
import javax.servlet.http.HttpServletRequest; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Collection; | ||
|
||
/** | ||
* A Filter that requires the request to be from within a specific set of IP | ||
* address ranges and / or not from with a specific (denied) set. | ||
* <p/> | ||
* Example config: | ||
* <pre> | ||
* [main] | ||
* localLan = org.apache.shiro.web.filter.authz.IpFilter | ||
* localLan.authorizedIps = 192.168.10.0/24 | ||
* localLan.deniedIps = 192.168.10.10/32 | ||
* <p/> | ||
* [urls] | ||
* /some/path/** = localLan | ||
* # override for just this path: | ||
* /another/path/** = localLan | ||
* </pre> | ||
* | ||
* @since 1.5 | ||
*/ | ||
public class IpFilter extends AuthorizationFilter { | ||
|
||
private static IpSource DEFAULT_IP_SOURCE = new IpSource() { | ||
public Collection<String> getAuthorizedIps() { | ||
return Collections.emptySet(); | ||
} | ||
public Collection<String> getDeniedIps() { | ||
return Collections.emptySet(); | ||
} | ||
}; | ||
|
||
private IpSource ipSource = DEFAULT_IP_SOURCE; | ||
|
||
private List<IpAddressMatcher> authorizedIpMatchers = Collections.emptyList(); | ||
private List<IpAddressMatcher> deniedIpMatchers = Collections.emptyList(); | ||
|
||
/** | ||
* Specifies a set of (comma, tab or space-separated) strings representing | ||
* IP address representing IPv4 or IPv6 ranges / CIDRs from which access | ||
* should be allowed (if the IP is not included in either the list of | ||
* statically defined denied IPs or the dynamic list of IPs obtained from | ||
* the IP source. | ||
*/ | ||
public void setAuthorizedIps(String authorizedIps) { | ||
String[] ips = StringUtils.tokenizeToStringArray(authorizedIps, ", \t"); | ||
if (ips != null && ips.length > 0) { | ||
authorizedIpMatchers = new ArrayList<IpAddressMatcher>(); | ||
for (String ip : ips) { | ||
authorizedIpMatchers.add(new IpAddressMatcher(ip)); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Specified a set of (comma, tab or space-separated) strings representing | ||
* IP address representing IPv4 or IPv6 ranges / CIDRs from which access | ||
* should be blocked. | ||
*/ | ||
public void setDeniedIps(String deniedIps) { | ||
String[] ips = StringUtils.tokenizeToStringArray(deniedIps, ", \t"); | ||
if (ips != null && ips.length > 0) { | ||
deniedIpMatchers = new ArrayList<IpAddressMatcher>(); | ||
for (String ip : ips) { | ||
deniedIpMatchers.add(new IpAddressMatcher(ip)); | ||
} | ||
} | ||
} | ||
|
||
public void setIpSource(IpSource source) { | ||
this.ipSource = source; | ||
} | ||
|
||
/** | ||
* Returns the remote host for a given HTTP request. By default uses the | ||
* remote method ServletRequest.getRemoteAddr(). May be overriden by | ||
* subclasses to obtain address information from specific headers (e.g. XFF | ||
* or Forwarded) in situations with reverse proxies. | ||
*/ | ||
public String getHostFromRequest(ServletRequest request) { | ||
return request.getRemoteAddr(); | ||
} | ||
|
||
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { | ||
String remoteIp = getHostFromRequest(request); | ||
for (IpAddressMatcher matcher : deniedIpMatchers) { | ||
if (matcher.matches(remoteIp)) { | ||
return false; | ||
} | ||
} | ||
for (String ip : ipSource.getDeniedIps()) { | ||
IpAddressMatcher matcher = new IpAddressMatcher(ip); | ||
if (matcher.matches(remoteIp)) { | ||
return false; | ||
} | ||
} | ||
for (IpAddressMatcher matcher : authorizedIpMatchers) { | ||
if (matcher.matches(remoteIp)) { | ||
return true; | ||
} | ||
} | ||
for (String ip : ipSource.getAuthorizedIps()) { | ||
IpAddressMatcher matcher = new IpAddressMatcher(ip); | ||
if (matcher.matches(remoteIp)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
web/src/main/java/org/apache/shiro/web/filter/authz/IpSource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* 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.shiro.web.filter.authz; | ||
|
||
import java.util.Collection; | ||
|
||
/** | ||
* Represents a source of information for IP restrictions (see IpFilter) | ||
* @since 1.5 | ||
*/ | ||
public interface IpSource { | ||
|
||
/** | ||
* Returns a set of strings representing IP address representing | ||
* IPv4 or IPv6 ranges / CIDRs. e.g. 192.168.0.0/16 from which | ||
* access should be allowed (if and only if the IP is not included | ||
* in the list of denied IPs) | ||
*/ | ||
public Collection<String> getAuthorizedIps(); | ||
|
||
/** | ||
* Returns a set of strings representing IP address representing | ||
* IPv4 or IPv6 ranges / CIDRs. e.g. 192.168.0.0/16 from which | ||
* access should be denied. | ||
*/ | ||
public Collection<String> getDeniedIps(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
web/src/test/java/org/apache/shiro/web/filter/authz/IpAddressMatcherTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright 2002-2016 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.apache.shiro.web.filter.authz; | ||
|
||
import static org.junit.Assert.assertTrue; | ||
import static org.junit.Assert.assertFalse; | ||
|
||
import org.junit.Test; | ||
|
||
/** | ||
* @since 1.5 | ||
*/ | ||
public class IpAddressMatcherTests { | ||
final IpAddressMatcher v6matcher = new IpAddressMatcher("fe80::21f:5bff:fe33:bd68"); | ||
final IpAddressMatcher v4matcher = new IpAddressMatcher("192.168.1.104"); | ||
final String ipv6Address = "fe80::21f:5bff:fe33:bd68"; | ||
final String ipv4Address = "192.168.1.104"; | ||
|
||
@Test | ||
public void ipv6MatcherMatchesIpv6Address() { | ||
assertTrue(v6matcher.matches(ipv6Address)); | ||
} | ||
|
||
@Test | ||
public void ipv6MatcherDoesntMatchIpv4Address() { | ||
assertFalse(v6matcher.matches(ipv4Address)); | ||
} | ||
|
||
@Test | ||
public void ipv4MatcherMatchesIpv4Address() { | ||
assertTrue(v4matcher.matches(ipv4Address)); | ||
} | ||
|
||
@Test | ||
public void ipv4SubnetMatchesCorrectly() throws Exception { | ||
IpAddressMatcher matcher = new IpAddressMatcher("192.168.1.0/24"); | ||
assertTrue(matcher.matches(ipv4Address)); | ||
matcher = new IpAddressMatcher("192.168.1.128/25"); | ||
assertFalse(matcher.matches(ipv4Address)); | ||
assertTrue(matcher.matches("192.168.1.159")); | ||
} | ||
|
||
@Test | ||
public void ipv6RangeMatches() throws Exception { | ||
IpAddressMatcher matcher = new IpAddressMatcher("2001:DB8::/48"); | ||
assertTrue(matcher.matches("2001:DB8:0:0:0:0:0:0")); | ||
assertTrue(matcher.matches("2001:DB8:0:0:0:0:0:1")); | ||
assertTrue(matcher.matches("2001:DB8:0:FFFF:FFFF:FFFF:FFFF:FFFF")); | ||
assertFalse(matcher.matches("2001:DB8:1:0:0:0:0:0")); | ||
} | ||
|
||
// https://github.com/spring-projects/spring-security/issues/1970q | ||
@Test | ||
public void zeroMaskMatchesAnything() throws Exception { | ||
IpAddressMatcher matcher = new IpAddressMatcher("0.0.0.0/0"); | ||
|
||
assertTrue(matcher.matches("123.4.5.6")); | ||
assertTrue(matcher.matches("192.168.0.159")); | ||
|
||
matcher = new IpAddressMatcher("192.168.0.159/0"); | ||
assertTrue(matcher.matches("123.4.5.6")); | ||
assertTrue(matcher.matches("192.168.0.159")); | ||
} | ||
} |
Oops, something went wrong.