Merge pull request #226 from bmhm/SHIRO-750
[SHIRO-750] update jax-rs dependency to jakarta.
diff --git a/NOTICE b/NOTICE
index afda186..9d26a95 100644
--- a/NOTICE
+++ b/NOTICE
@@ -9,7 +9,7 @@
available at http://www.javaspecialists.eu/archive/Issue015.html,
with continued modifications.
-Certain parts (StringUtils etc.) of the source code for this
-product was copied for simplicity and to reduce dependencies
-from the source code developed by the Spring Framework Project
-(http://www.springframework.org).
+Certain parts (StringUtils, IpAddressMatcher, etc.) of the source
+code for this product was copied for simplicity and to reduce
+dependencies from the source code developed by the Spring Framework
+Project (http://www.springframework.org).
diff --git a/lang/src/main/java/org/apache/shiro/codec/Base64.java b/lang/src/main/java/org/apache/shiro/codec/Base64.java
index da31059..b277b13 100644
--- a/lang/src/main/java/org/apache/shiro/codec/Base64.java
+++ b/lang/src/main/java/org/apache/shiro/codec/Base64.java
@@ -39,190 +39,6 @@
public class Base64 {
/**
- * Chunk size per RFC 2045 section 6.8.
- * <p/>
- * The character limit does not count the trailing CRLF, but counts all other characters, including any
- * equal signs.
- *
- * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a>
- */
- static final int CHUNK_SIZE = 76;
-
- /**
- * Chunk separator per RFC 2045 section 2.1.
- *
- * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a>
- */
- static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes();
-
- /**
- * The base length.
- */
- private static final int BASELENGTH = 255;
-
- /**
- * Lookup length.
- */
- private static final int LOOKUPLENGTH = 64;
-
- /**
- * Used to calculate the number of bits in a byte.
- */
- private static final int EIGHTBIT = 8;
-
- /**
- * Used when encoding something which has fewer than 24 bits.
- */
- private static final int SIXTEENBIT = 16;
-
- /**
- * Used to determine how many bits data contains.
- */
- private static final int TWENTYFOURBITGROUP = 24;
-
- /**
- * Used to get the number of Quadruples.
- */
- private static final int FOURBYTE = 4;
-
- /**
- * Used to test the sign of a byte.
- */
- private static final int SIGN = -128;
-
- /**
- * Byte used to pad output.
- */
- private static final byte PAD = (byte) '=';
-
- /**
- * Contains the Base64 values <code>0</code> through <code>63</code> accessed by using character encodings as
- * indices.
- * <p/>
- * <p>For example, <code>base64Alphabet['+']</code> returns <code>62</code>.</p>
- * <p/>
- * <p>The value of undefined encodings is <code>-1</code>.</p>
- */
- private static final byte[] base64Alphabet = new byte[BASELENGTH];
-
- /**
- * <p>Contains the Base64 encodings <code>A</code> through <code>Z</code>, followed by <code>a</code> through
- * <code>z</code>, followed by <code>0</code> through <code>9</code>, followed by <code>+</code>, and
- * <code>/</code>.</p>
- * <p/>
- * <p>This array is accessed by using character values as indices.</p>
- * <p/>
- * <p>For example, <code>lookUpBase64Alphabet[62] </code> returns <code>'+'</code>.</p>
- */
- private static final byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH];
-
- // Populating the lookup and character arrays
-
- static {
- for (int i = 0; i < BASELENGTH; i++) {
- base64Alphabet[i] = (byte) -1;
- }
- for (int i = 'Z'; i >= 'A'; i--) {
- base64Alphabet[i] = (byte) (i - 'A');
- }
- for (int i = 'z'; i >= 'a'; i--) {
- base64Alphabet[i] = (byte) (i - 'a' + 26);
- }
- for (int i = '9'; i >= '0'; i--) {
- base64Alphabet[i] = (byte) (i - '0' + 52);
- }
-
- base64Alphabet['+'] = 62;
- base64Alphabet['/'] = 63;
-
- for (int i = 0; i <= 25; i++) {
- lookUpBase64Alphabet[i] = (byte) ('A' + i);
- }
-
- for (int i = 26, j = 0; i <= 51; i++, j++) {
- lookUpBase64Alphabet[i] = (byte) ('a' + j);
- }
-
- for (int i = 52, j = 0; i <= 61; i++, j++) {
- lookUpBase64Alphabet[i] = (byte) ('0' + j);
- }
-
- lookUpBase64Alphabet[62] = (byte) '+';
- lookUpBase64Alphabet[63] = (byte) '/';
- }
-
- /**
- * Returns whether or not the <code>octet</code> is in the base 64 alphabet.
- *
- * @param octect The value to test
- * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
- */
- private static boolean isBase64(byte octect) {
- if (octect == PAD) {
- return true;
- } else //noinspection RedundantIfStatement
- if (octect < 0 || base64Alphabet[octect] == -1) {
- return false;
- } else {
- return true;
- }
- }
-
- /**
- * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet.
- *
- * @param arrayOctect byte array to test
- * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is
- * empty; false, otherwise
- */
- public static boolean isBase64(byte[] arrayOctect) {
-
- arrayOctect = discardWhitespace(arrayOctect);
-
- int length = arrayOctect.length;
- if (length == 0) {
- // shouldn't a 0 length array be valid base64 data?
- // return false;
- return true;
- }
- for (int i = 0; i < length; i++) {
- if (!isBase64(arrayOctect[i])) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Discards any whitespace from a base-64 encoded block.
- *
- * @param data The base-64 encoded data to discard the whitespace from.
- * @return The data, less whitespace (see RFC 2045).
- */
- static byte[] discardWhitespace(byte[] data) {
- byte groomedData[] = new byte[data.length];
- int bytesCopied = 0;
-
- for (byte aByte : data) {
- switch (aByte) {
- case (byte) ' ':
- case (byte) '\n':
- case (byte) '\r':
- case (byte) '\t':
- break;
- default:
- groomedData[bytesCopied++] = aByte;
- }
- }
-
- byte packedData[] = new byte[bytesCopied];
-
- System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
-
- return packedData;
- }
-
- /**
* Base64 encodes the specified byte array and then encodes it as a String using Shiro's preferred character
* encoding (UTF-8).
*
@@ -235,151 +51,15 @@
}
/**
- * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks
- *
- * @param binaryData binary data to encodeToChars
- * @return Base64 characters chunked in 76 character blocks
- */
- public static byte[] encodeChunked(byte[] binaryData) {
- return encode(binaryData, true);
- }
-
- /**
* Encodes a byte[] containing binary data, into a byte[] containing characters in the Base64 alphabet.
*
* @param pArray a byte array containing binary data
* @return A byte array containing only Base64 character data
*/
public static byte[] encode(byte[] pArray) {
- return encode(pArray, false);
+ return java.util.Base64.getEncoder().encode(pArray);
}
- /**
- * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks.
- *
- * @param binaryData Array containing binary data to encodeToChars.
- * @param isChunked if <code>true</code> this encoder will chunk the base64 output into 76 character blocks
- * @return Base64-encoded data.
- * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE}
- */
- public static byte[] encode(byte[] binaryData, boolean isChunked) {
- long binaryDataLength = binaryData.length;
- long lengthDataBits = binaryDataLength * EIGHTBIT;
- long fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
- long tripletCount = lengthDataBits / TWENTYFOURBITGROUP;
- long encodedDataLengthLong;
- int chunckCount = 0;
-
- if (fewerThan24bits != 0) {
- // data not divisible by 24 bit
- encodedDataLengthLong = (tripletCount + 1) * 4;
- } else {
- // 16 or 8 bit
- encodedDataLengthLong = tripletCount * 4;
- }
-
- // If the output is to be "chunked" into 76 character sections,
- // for compliance with RFC 2045 MIME, then it is important to
- // allow for extra length to account for the separator(s)
- if (isChunked) {
-
- chunckCount = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math
- .ceil((float) encodedDataLengthLong / CHUNK_SIZE));
- encodedDataLengthLong += chunckCount * CHUNK_SEPARATOR.length;
- }
-
- if (encodedDataLengthLong > Integer.MAX_VALUE) {
- throw new IllegalArgumentException(
- "Input array too big, output array would be bigger than Integer.MAX_VALUE=" + Integer.MAX_VALUE);
- }
- int encodedDataLength = (int) encodedDataLengthLong;
- byte encodedData[] = new byte[encodedDataLength];
-
- byte k, l, b1, b2, b3;
-
- int encodedIndex = 0;
- int dataIndex;
- int i;
- int nextSeparatorIndex = CHUNK_SIZE;
- int chunksSoFar = 0;
-
- // log.debug("number of triplets = " + numberTriplets);
- for (i = 0; i < tripletCount; i++) {
- dataIndex = i * 3;
- b1 = binaryData[dataIndex];
- b2 = binaryData[dataIndex + 1];
- b3 = binaryData[dataIndex + 2];
-
- // log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3);
-
- l = (byte) (b2 & 0x0f);
- k = (byte) (b1 & 0x03);
-
- byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
- byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
- byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
-
- encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
- // log.debug( "val2 = " + val2 );
- // log.debug( "k4 = " + (k<<4) );
- // log.debug( "vak = " + (val2 | (k<<4)) );
- encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
- encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3];
- encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];
-
- encodedIndex += 4;
-
- // If we are chunking, let's put a chunk separator down.
- if (isChunked) {
- // this assumes that CHUNK_SIZE % 4 == 0
- if (encodedIndex == nextSeparatorIndex) {
- System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedIndex, CHUNK_SEPARATOR.length);
- chunksSoFar++;
- nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + (chunksSoFar * CHUNK_SEPARATOR.length);
- encodedIndex += CHUNK_SEPARATOR.length;
- }
- }
- }
-
- // form integral number of 6-bit groups
- dataIndex = i * 3;
-
- if (fewerThan24bits == EIGHTBIT) {
- b1 = binaryData[dataIndex];
- k = (byte) (b1 & 0x03);
- // log.debug("b1=" + b1);
- // log.debug("b1<<2 = " + (b1>>2) );
- byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
- encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
- encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
- encodedData[encodedIndex + 2] = PAD;
- encodedData[encodedIndex + 3] = PAD;
- } else if (fewerThan24bits == SIXTEENBIT) {
-
- b1 = binaryData[dataIndex];
- b2 = binaryData[dataIndex + 1];
- l = (byte) (b2 & 0x0f);
- k = (byte) (b1 & 0x03);
-
- byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
- byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
-
- encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
- encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)];
- encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
- encodedData[encodedIndex + 3] = PAD;
- }
-
- if (isChunked) {
- // we also add a separator to the end of the final chunk.
- if (chunksSoFar < chunckCount) {
- System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedDataLength - CHUNK_SEPARATOR.length,
- CHUNK_SEPARATOR.length);
- }
- }
-
- return encodedData;
- }
/**
* Converts the specified UTF-8 Base64 encoded String and decodes it to a resultant UTF-8 encoded string.
@@ -421,86 +101,7 @@
* @return Array containing decoded data.
*/
public static byte[] decode(byte[] base64Data) {
- // RFC 2045 requires that we discard ALL non-Base64 characters
- base64Data = discardNonBase64(base64Data);
-
- // handle the edge case, so we don't have to worry about it later
- if (base64Data.length == 0) {
- return new byte[0];
- }
-
- int numberQuadruple = base64Data.length / FOURBYTE;
- byte decodedData[];
- byte b1, b2, b3, b4, marker0, marker1;
-
- // Throw away anything not in base64Data
-
- int encodedIndex = 0;
- int dataIndex;
- {
- // this sizes the output array properly - rlw
- int lastData = base64Data.length;
- // ignore the '=' padding
- while (base64Data[lastData - 1] == PAD) {
- if (--lastData == 0) {
- return new byte[0];
- }
- }
- decodedData = new byte[lastData - numberQuadruple];
- }
-
- for (int i = 0; i < numberQuadruple; i++) {
- dataIndex = i * 4;
- marker0 = base64Data[dataIndex + 2];
- marker1 = base64Data[dataIndex + 3];
-
- b1 = base64Alphabet[base64Data[dataIndex]];
- b2 = base64Alphabet[base64Data[dataIndex + 1]];
-
- if (marker0 != PAD && marker1 != PAD) {
- // No PAD e.g 3cQl
- b3 = base64Alphabet[marker0];
- b4 = base64Alphabet[marker1];
-
- decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
- decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
- decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
- } else if (marker0 == PAD) {
- // Two PAD e.g. 3c[Pad][Pad]
- decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
- } else {
- // One PAD e.g. 3cQ[Pad]
- b3 = base64Alphabet[marker0];
- decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
- decodedData[encodedIndex + 1] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
- }
- encodedIndex += 3;
- }
- return decodedData;
- }
-
- /**
- * Discards any characters outside of the base64 alphabet, per the requirements on page 25 of RFC 2045 - "Any
- * characters outside of the base64 alphabet are to be ignored in base64 encoded data."
- *
- * @param data The base-64 encoded data to groom
- * @return The data, less non-base64 characters (see RFC 2045).
- */
- static byte[] discardNonBase64(byte[] data) {
- byte groomedData[] = new byte[data.length];
- int bytesCopied = 0;
-
- for (byte aByte : data) {
- if (isBase64(aByte)) {
- groomedData[bytesCopied++] = aByte;
- }
- }
-
- byte packedData[] = new byte[bytesCopied];
-
- System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
-
- return packedData;
+ return java.util.Base64.getDecoder().decode(base64Data);
}
}
diff --git a/pom.xml b/pom.xml
index 0ecade0..e154ead 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,7 +78,6 @@
<!-- Don't change this version without also changing the shiro-aspect and shiro-features
modules' OSGi metadata: -->
<aspectj.version>1.9.4</aspectj.version>
- <cas.client.core.version>3.2.2</cas.client.core.version>
<commons.beanutils.version>1.9.4</commons.beanutils.version>
<commons.cli.version>1.4</commons.cli.version>
<commons.codec.version>1.14</commons.codec.version>
@@ -750,11 +749,6 @@
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
- <artifactId>shiro-cas</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${project.version}</version>
</dependency>
@@ -847,13 +841,6 @@
<version>${javax.annotation.api.version}</version>
</dependency>
<dependency>
- <!-- runtime dependency for the shiro-cas module: -->
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- <version>${commons.codec.version}</version>
- <scope>runtime</scope>
- </dependency>
- <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
diff --git a/src/owasp-suppression.xml b/src/owasp-suppression.xml
index 76e2b0b..03ae97d 100644
--- a/src/owasp-suppression.xml
+++ b/src/owasp-suppression.xml
@@ -31,10 +31,4 @@
<cpe>cpe:/a:apache:tomcat:8.5.5</cpe>
</suppress>
- <suppress>
- <notes><![CDATA[ file name: opensaml-1.1.jar ]]></notes>
- <sha1>21ec22368b6baa211a29887e162aa4cf9a8f3c60</sha1>
- <cpe>cpe:/a:internet2:opensaml:1.1</cpe>
- </suppress>
-
-</suppressions>
\ No newline at end of file
+</suppressions>
diff --git a/support/cas/pom.xml b/support/cas/pom.xml
deleted file mode 100644
index c0909ae..0000000
--- a/support/cas/pom.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ 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.
- -->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
-
- <parent>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-support</artifactId>
- <version>2.0.0-SNAPSHOT</version>
- <relativePath>../pom.xml</relativePath>
- </parent>
-
- <modelVersion>4.0.0</modelVersion>
- <artifactId>shiro-cas</artifactId>
- <name>Apache Shiro :: Support :: CAS</name>
- <packaging>bundle</packaging>
-
- <dependencies>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.jasig.cas.client</groupId>
- <artifactId>cas-client-core</artifactId>
- <version>${cas.client.core.version}</version>
- </dependency>
- <dependency>
- <!-- for Optional SAML ticket validation: -->
- <groupId>commons-codec</groupId>
- <artifactId>commons-codec</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <!-- for Optional SAML ticket validation: -->
- <groupId>org.opensaml</groupId>
- <artifactId>opensaml</artifactId>
- <version>1.1</version>
- <scope>runtime</scope>
- <optional>true</optional>
- </dependency>
- <dependency>
- <!-- for Optional SAML ticket validation: -->
- <groupId>org.apache.santuario</groupId>
- <artifactId>xmlsec</artifactId>
- <version>2.1.4</version>
- <scope>runtime</scope>
- <optional>true</optional>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <extensions>true</extensions>
- <configuration>
- <instructions>
- <Bundle-SymbolicName>org.apache.shiro.cas</Bundle-SymbolicName>
- <Export-Package>org.apache.shiro.cas*;version=${project.version}</Export-Package>
- <Import-Package>
- org.apache.shiro*;version="${shiro.osgi.importRange}",
- org.jasig.cas.client*;version="[3.2, 4)",
- *
- </Import-Package>
- </instructions>
- </configuration>
- </plugin>
- </plugins>
- </build>
-
-</project>
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java b/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
deleted file mode 100644
index e3add40..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasAuthenticationException.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.cas;
-
-import org.apache.shiro.authc.AuthenticationException;
-
-/**
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasAuthenticationException extends AuthenticationException {
-
- public CasAuthenticationException() {
- super();
- }
-
- public CasAuthenticationException(String message) {
- super(message);
- }
-
- public CasAuthenticationException(Throwable cause) {
- super(cause);
- }
-
- public CasAuthenticationException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java b/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
deleted file mode 100644
index 88262a8..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasFilter.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.cas;
-
-import org.apache.shiro.authc.AuthenticationException;
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.subject.Subject;
-import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
-import org.apache.shiro.web.util.WebUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-
-/**
- * This filter validates the CAS service ticket to authenticate the user. It must be configured on the URL recognized
- * by the CAS server. For example, in {@code shiro.ini}:
- * <pre>
- * [main]
- * casFilter = org.apache.shiro.cas.CasFilter
- * ...
- *
- * [urls]
- * /shiro-cas = casFilter
- * ...
- * </pre>
- * (example : http://host:port/mycontextpath/shiro-cas)
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasFilter extends AuthenticatingFilter {
-
- private static Logger logger = LoggerFactory.getLogger(CasFilter.class);
-
- // the name of the parameter service ticket in url
- private static final String TICKET_PARAMETER = "ticket";
-
- // the url where the application is redirected if the CAS service ticket validation failed (example : /mycontextpatch/cas_error.jsp)
- private String failureUrl;
-
- /**
- * The token created for this authentication is a CasToken containing the CAS service ticket received on the CAS service url (on which
- * the filter must be configured).
- *
- * @param request the incoming request
- * @param response the outgoing response
- * @throws Exception if there is an error processing the request.
- */
- @Override
- protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
- HttpServletRequest httpRequest = (HttpServletRequest) request;
- String ticket = httpRequest.getParameter(TICKET_PARAMETER);
- return new CasToken(ticket);
- }
-
- /**
- * Execute login by creating {@link #createToken(javax.servlet.ServletRequest, javax.servlet.ServletResponse) token} and logging subject
- * with this token.
- *
- * @param request the incoming request
- * @param response the outgoing response
- * @throws Exception if there is an error processing the request.
- */
- @Override
- protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
- return executeLogin(request, response);
- }
-
- /**
- * Returns <code>false</code> to always force authentication (user is never considered authenticated by this filter).
- *
- * @param request the incoming request
- * @param response the outgoing response
- * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
- * @return <code>false</code>
- */
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
- return false;
- }
-
- /**
- * If login has been successful, redirect user to the original protected url.
- *
- * @param token the token representing the current authentication
- * @param subject the current authenticated subjet
- * @param request the incoming request
- * @param response the outgoing response
- * @throws Exception if there is an error processing the request.
- */
- @Override
- protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
- ServletResponse response) throws Exception {
- issueSuccessRedirect(request, response);
- return false;
- }
-
- /**
- * If login has failed, redirect user to the CAS error page (no ticket or ticket validation failed) except if the user is already
- * authenticated, in which case redirect to the default success url.
- *
- * @param token the token representing the current authentication
- * @param ae the current authentication exception
- * @param request the incoming request
- * @param response the outgoing response
- */
- @Override
- protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
- ServletResponse response) {
- if (logger.isDebugEnabled()) {
- logger.debug( "Authentication exception", ae );
- }
- // is user authenticated or in remember me mode ?
- Subject subject = getSubject(request, response);
- if (subject.isAuthenticated() || subject.isRemembered()) {
- try {
- issueSuccessRedirect(request, response);
- } catch (Exception e) {
- logger.error("Cannot redirect to the default success url", e);
- }
- } else {
- try {
- WebUtils.issueRedirect(request, response, failureUrl);
- } catch (IOException e) {
- logger.error("Cannot redirect to failure url : {}", failureUrl, e);
- }
- }
- return false;
- }
-
- public void setFailureUrl(String failureUrl) {
- this.failureUrl = failureUrl;
- }
-}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java b/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
deleted file mode 100644
index 791674a..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasRealm.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * 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.cas;
-
-import org.apache.shiro.authc.AuthenticationException;
-import org.apache.shiro.authc.AuthenticationInfo;
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.authc.SimpleAuthenticationInfo;
-import org.apache.shiro.authz.AuthorizationInfo;
-import org.apache.shiro.authz.SimpleAuthorizationInfo;
-import org.apache.shiro.realm.AuthorizingRealm;
-import org.apache.shiro.subject.PrincipalCollection;
-import org.apache.shiro.subject.SimplePrincipalCollection;
-import org.apache.shiro.util.CollectionUtils;
-import org.apache.shiro.util.StringUtils;
-import org.jasig.cas.client.authentication.AttributePrincipal;
-import org.jasig.cas.client.validation.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This realm implementation acts as a CAS client to a CAS server for authentication and basic authorization.
- * <p/>
- * This realm functions by inspecting a submitted {@link org.apache.shiro.cas.CasToken CasToken} (which essentially
- * wraps a CAS service ticket) and validates it against the CAS server using a configured CAS
- * {@link org.jasig.cas.client.validation.TicketValidator TicketValidator}.
- * <p/>
- * The {@link #getValidationProtocol() validationProtocol} is {@code CAS} by default, which indicates that a
- * a {@link org.jasig.cas.client.validation.Cas20ServiceTicketValidator Cas20ServiceTicketValidator}
- * will be used for ticket validation. You can alternatively set
- * or {@link org.jasig.cas.client.validation.Saml11TicketValidator Saml11TicketValidator} of CAS client. It is based on
- * {@link AuthorizingRealm AuthorizingRealm} for both authentication and authorization. User id and attributes are retrieved from the CAS
- * service ticket validation response during authentication phase. Roles and permissions are computed during authorization phase (according
- * to the attributes previously retrieved).
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasRealm extends AuthorizingRealm {
-
- // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
- public static final String DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed";
- public static final String DEFAULT_VALIDATION_PROTOCOL = "CAS";
-
- private static Logger log = LoggerFactory.getLogger(CasRealm.class);
-
- // this is the url of the CAS server (example : http://host:port/cas)
- private String casServerUrlPrefix;
-
- // this is the CAS service url of the application (example : http://host:port/mycontextpath/shiro-cas)
- private String casService;
-
- /* CAS protocol to use for ticket validation : CAS (default) or SAML :
- - CAS protocol can be used with CAS server version < 3.1 : in this case, no user attributes can be retrieved from the CAS ticket validation response (except if there are some customizations on CAS server side)
- - SAML protocol can be used with CAS server version >= 3.1 : in this case, user attributes can be extracted from the CAS ticket validation response
- */
- private String validationProtocol = DEFAULT_VALIDATION_PROTOCOL;
-
- // default name of the CAS attribute for remember me authentication (CAS 3.4.10+)
- private String rememberMeAttributeName = DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME;
-
- // this class from the CAS client is used to validate a service ticket on CAS server
- private TicketValidator ticketValidator;
-
- // default roles to applied to authenticated user
- private String defaultRoles;
-
- // default permissions to applied to authenticated user
- private String defaultPermissions;
-
- // names of attributes containing roles
- private String roleAttributeNames;
-
- // names of attributes containing permissions
- private String permissionAttributeNames;
-
- public CasRealm() {
- setAuthenticationTokenClass(CasToken.class);
- }
-
- @Override
- protected void onInit() {
- super.onInit();
- ensureTicketValidator();
- }
-
- protected TicketValidator ensureTicketValidator() {
- if (this.ticketValidator == null) {
- this.ticketValidator = createTicketValidator();
- }
- return this.ticketValidator;
- }
-
- protected TicketValidator createTicketValidator() {
- String urlPrefix = getCasServerUrlPrefix();
- if ("saml".equalsIgnoreCase(getValidationProtocol())) {
- return new Saml11TicketValidator(urlPrefix);
- }
- return new Cas20ServiceTicketValidator(urlPrefix);
- }
-
- /**
- * Authenticates a user and retrieves its information.
- *
- * @param token the authentication token
- * @throws AuthenticationException if there is an error during authentication.
- */
- @Override
- @SuppressWarnings("unchecked")
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- CasToken casToken = (CasToken) token;
- if (token == null) {
- return null;
- }
-
- String ticket = (String)casToken.getCredentials();
- if (!StringUtils.hasText(ticket)) {
- return null;
- }
-
- TicketValidator ticketValidator = ensureTicketValidator();
-
- try {
- // contact CAS server to validate service ticket
- Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
- // get principal, user id and attributes
- AttributePrincipal casPrincipal = casAssertion.getPrincipal();
- String userId = casPrincipal.getName();
- log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{
- ticket, getCasServerUrlPrefix(), userId
- });
-
- Map<String, Object> attributes = casPrincipal.getAttributes();
- // refresh authentication token (user id + remember me)
- casToken.setUserId(userId);
- String rememberMeAttributeName = getRememberMeAttributeName();
- String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
- boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
- if (isRemembered) {
- casToken.setRememberMe(true);
- }
- // create simple authentication info
- List<Object> principals = CollectionUtils.asList(userId, attributes);
- PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
- return new SimpleAuthenticationInfo(principalCollection, ticket);
- } catch (TicketValidationException e) {
- throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
- }
- }
-
- /**
- * Retrieves the AuthorizationInfo for the given principals (the CAS previously authenticated user : id + attributes).
- *
- * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
- * @return the AuthorizationInfo associated with this principals.
- */
- @Override
- @SuppressWarnings("unchecked")
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- // retrieve user information
- SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
- List<Object> listPrincipals = principalCollection.asList();
- Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1);
- // create simple authorization info
- SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
- // add default roles
- addRoles(simpleAuthorizationInfo, split(defaultRoles));
- // add default permissions
- addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
- // get roles from attributes
- List<String> attributeNames = split(roleAttributeNames);
- for (String attributeName : attributeNames) {
- String value = attributes.get(attributeName);
- addRoles(simpleAuthorizationInfo, split(value));
- }
- // get permissions from attributes
- attributeNames = split(permissionAttributeNames);
- for (String attributeName : attributeNames) {
- String value = attributes.get(attributeName);
- addPermissions(simpleAuthorizationInfo, split(value));
- }
- return simpleAuthorizationInfo;
- }
-
- /**
- * Split a string into a list of not empty and trimmed strings, delimiter is a comma.
- *
- * @param s the input string
- * @return the list of not empty and trimmed strings
- */
- private List<String> split(String s) {
- List<String> list = new ArrayList<String>();
- String[] elements = StringUtils.split(s, ',');
- if (elements != null && elements.length > 0) {
- for (String element : elements) {
- if (StringUtils.hasText(element)) {
- list.add(element.trim());
- }
- }
- }
- return list;
- }
-
- /**
- * Add roles to the simple authorization info.
- *
- * @param simpleAuthorizationInfo
- * @param roles the list of roles to add
- */
- private void addRoles(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> roles) {
- for (String role : roles) {
- simpleAuthorizationInfo.addRole(role);
- }
- }
-
- /**
- * Add permissions to the simple authorization info.
- *
- * @param simpleAuthorizationInfo
- * @param permissions the list of permissions to add
- */
- private void addPermissions(SimpleAuthorizationInfo simpleAuthorizationInfo, List<String> permissions) {
- for (String permission : permissions) {
- simpleAuthorizationInfo.addStringPermission(permission);
- }
- }
-
- public String getCasServerUrlPrefix() {
- return casServerUrlPrefix;
- }
-
- public void setCasServerUrlPrefix(String casServerUrlPrefix) {
- this.casServerUrlPrefix = casServerUrlPrefix;
- }
-
- public String getCasService() {
- return casService;
- }
-
- public void setCasService(String casService) {
- this.casService = casService;
- }
-
- public String getValidationProtocol() {
- return validationProtocol;
- }
-
- public void setValidationProtocol(String validationProtocol) {
- this.validationProtocol = validationProtocol;
- }
-
- public String getRememberMeAttributeName() {
- return rememberMeAttributeName;
- }
-
- public void setRememberMeAttributeName(String rememberMeAttributeName) {
- this.rememberMeAttributeName = rememberMeAttributeName;
- }
-
- public String getDefaultRoles() {
- return defaultRoles;
- }
-
- public void setDefaultRoles(String defaultRoles) {
- this.defaultRoles = defaultRoles;
- }
-
- public String getDefaultPermissions() {
- return defaultPermissions;
- }
-
- public void setDefaultPermissions(String defaultPermissions) {
- this.defaultPermissions = defaultPermissions;
- }
-
- public String getRoleAttributeNames() {
- return roleAttributeNames;
- }
-
- public void setRoleAttributeNames(String roleAttributeNames) {
- this.roleAttributeNames = roleAttributeNames;
- }
-
- public String getPermissionAttributeNames() {
- return permissionAttributeNames;
- }
-
- public void setPermissionAttributeNames(String permissionAttributeNames) {
- this.permissionAttributeNames = permissionAttributeNames;
- }
-}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java b/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
deleted file mode 100644
index 51a774e..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasSubjectFactory.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.cas;
-
-import org.apache.shiro.authc.AuthenticationToken;
-import org.apache.shiro.subject.Subject;
-import org.apache.shiro.subject.SubjectContext;
-import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
-
-/**
- * {@link org.apache.shiro.mgt.SubjectFactory Subject} implementation to be used in CAS-enabled applications.
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasSubjectFactory extends DefaultWebSubjectFactory {
-
- @Override
- public Subject createSubject(SubjectContext context) {
-
- //the authenticated flag is only set by the SecurityManager after a successful authentication attempt.
- boolean authenticated = context.isAuthenticated();
-
- //although the SecurityManager 'sees' the submission as a successful authentication, in reality, the
- //login might have been just a CAS rememberMe login. If so, set the authenticated flag appropriately:
- if (authenticated) {
-
- AuthenticationToken token = context.getAuthenticationToken();
-
- if (token != null && token instanceof CasToken) {
- CasToken casToken = (CasToken) token;
- // set the authenticated flag of the context to true only if the CAS subject is not in a remember me mode
- if (casToken.isRememberMe()) {
- context.setAuthenticated(false);
- }
- }
- }
-
- return super.createSubject(context);
- }
-}
diff --git a/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java b/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
deleted file mode 100644
index 221d1cb..0000000
--- a/support/cas/src/main/java/org/apache/shiro/cas/CasToken.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.cas;
-
-import org.apache.shiro.authc.RememberMeAuthenticationToken;
-
-/**
- * This class represents a token for a CAS authentication (service ticket + user id + remember me).
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-public class CasToken implements RememberMeAuthenticationToken {
-
- private static final long serialVersionUID = 8587329689973009598L;
-
- // the service ticket returned by the CAS server
- private String ticket = null;
-
- // the user identifier
- private String userId = null;
-
- // is the user in a remember me mode ?
- private boolean isRememberMe = false;
-
- public CasToken(String ticket) {
- this.ticket = ticket;
- }
-
- public Object getPrincipal() {
- return userId;
- }
-
- public Object getCredentials() {
- return ticket;
- }
-
- public void setUserId(String userId) {
- this.userId = userId;
- }
-
- public boolean isRememberMe() {
- return isRememberMe;
- }
-
- public void setRememberMe(boolean isRememberMe) {
- this.isRememberMe = isRememberMe;
- }
-}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
deleted file mode 100644
index b917638..0000000
--- a/support/cas/src/test/groovy/org/apache/shiro/cas/CasRealmTest.groovy
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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.cas
-
-import org.apache.shiro.authc.AuthenticationInfo
-import org.apache.shiro.authz.AuthorizationInfo
-
-import org.junit.Test
-
-import static org.junit.Assert.*
-
-/**
- * Unit tests for the {@link CasRealm} implementation.
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-class CasRealmTest {
-
- /**
- * Creates a CAS realm with a ticket validator mock.
- *
- * @return CasRealm The CAS realm for testing.
- */
- private CasRealm createCasRealm() {
- new CasRealm(ticketValidator: new MockServiceTicketValidator());
- }
-
- @Test
- void testNoAttribute() {
- CasRealm casRealm = createCasRealm();
- CasToken casToken = new CasToken('$=defaultId');
- AuthenticationInfo authenticationInfo = casRealm.doGetAuthenticationInfo(casToken);
- def principals = authenticationInfo.principals
- assertEquals "defaultId", principals.primaryPrincipal
- def attributes = principals.asList()[1] //returns a map
- assertEquals 0, attributes.size()
- AuthorizationInfo authorizationInfo = casRealm.doGetAuthorizationInfo(principals);
- assertNull authorizationInfo.stringPermissions
- assertNull authorizationInfo.roles
- }
-
- @Test
- void testNoAttributeDefaultRoleAndPermission() {
- CasRealm casRealm = createCasRealm();
- casRealm.defaultRoles = "defaultRole"
- casRealm.defaultPermissions = "defaultPermission"
- CasToken casToken = new CasToken('$=defaultId');
- AuthenticationInfo authenticationInfo = casRealm.doGetAuthenticationInfo(casToken);
- def principals = authenticationInfo.principals
- assertEquals "defaultId", principals.primaryPrincipal
- def attributes = principals.oneByType(Map)
- assertEquals 0, attributes.size()
- AuthorizationInfo authorizationInfo = casRealm.doGetAuthorizationInfo(principals);
- assertTrue authorizationInfo.roles.contains("defaultRole")
- assertTrue authorizationInfo.stringPermissions.contains("defaultPermission")
- }
-
- @Test
- void testNoAttributeDefaultRolesAndPermissions() {
- CasRealm casRealm = createCasRealm();
- casRealm.defaultRoles = "defaultRole1, defaultRole2"
- casRealm.defaultPermissions = "defaultPermission1,defaultPermission2"
- CasToken casToken = new CasToken('$=defaultId');
- AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
- def principals = authcInfo.principals
- assertEquals "defaultId", principals.primaryPrincipal
- def attributes = principals.oneByType(Map)
- assertEquals 0, attributes.size()
- AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals)
- assertEquals 2, authzInfo.roles.size()
- assertTrue authzInfo.roles.contains("defaultRole1")
- assertTrue authzInfo.roles.contains("defaultRole2")
- assertEquals 2, authzInfo.stringPermissions.size()
- assertTrue authzInfo.stringPermissions.contains("defaultPermission1")
- assertTrue authzInfo.stringPermissions.contains("defaultPermission2")
- }
-
- @Test
- void testRoleAndPermission() {
- CasRealm casRealm = createCasRealm();
- casRealm.roleAttributeNames = "role"
- casRealm.permissionAttributeNames = "permission"
- CasToken casToken = new CasToken('$=defaultId|role=aRole|permission=aPermission');
- AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
- def principals = authcInfo.principals
- assertEquals "defaultId", principals.primaryPrincipal
- def attributes = principals.oneByType(Map)
- assertEquals 2, attributes.size()
- assertEquals "aRole", attributes['role']
- assertEquals "aPermission", attributes['permission']
- AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
- assertTrue authzInfo.roles.contains("aRole")
- assertTrue authzInfo.stringPermissions.contains("aPermission")
- }
-
- @Test
- void testRolesAndPermissions() {
- CasRealm casRealm = createCasRealm();
- casRealm.setRoleAttributeNames("role1 , role2");
- casRealm.setPermissionAttributeNames("permission1,permission2");
- CasToken casToken = new CasToken(
- '$=defaultId|role1=role11 , role12|role2=role21,role22|permission1=permission11, permission12|permission2=permission21 ,permission22');
- AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
- def principals = authcInfo.principals
- assertEquals "defaultId", principals.primaryPrincipal
- def attributes = principals.oneByType(Map)
- assertEquals "role11 , role12", attributes['role1']
- assertEquals "role21,role22", attributes['role2']
- assertEquals "permission11, permission12", attributes['permission1']
- assertEquals "permission21 ,permission22", attributes['permission2']
- AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
- assertEquals 4, authzInfo.roles.size()
- assertTrue authzInfo.roles.contains("role11")
- assertTrue authzInfo.roles.contains("role12")
- assertTrue authzInfo.roles.contains("role21")
- assertTrue authzInfo.roles.contains("role22")
- assertTrue authzInfo.stringPermissions.contains("permission11")
- assertTrue authzInfo.stringPermissions.contains("permission12")
- assertTrue authzInfo.stringPermissions.contains("permission21")
- assertTrue authzInfo.stringPermissions.contains("permission22")
- }
-
- @Test
- void testNotRememberMe() {
- CasRealm casRealm = createCasRealm();
- CasToken casToken = new CasToken("\$=defaultId|$CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME=false");
- AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
- def principals = authcInfo.principals
- assertEquals "defaultId", principals.primaryPrincipal
- def attributes = principals.oneByType(Map)
- assertEquals "false", attributes[CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME]
- assertFalse casToken.rememberMe
- AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
- assertNull authzInfo.stringPermissions
- assertNull authzInfo.roles
- }
-
- @Test
- void testRememberMe() {
- CasRealm casRealm = createCasRealm();
- CasToken casToken = new CasToken("\$=defaultId|$CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME=true");
- AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
- def principals = authcInfo.principals
- assertEquals "defaultId", principals.primaryPrincipal
- def attributes = principals.oneByType(Map)
- assertEquals "true", attributes[CasRealm.DEFAULT_REMEMBER_ME_ATTRIBUTE_NAME]
- assertTrue casToken.rememberMe
- AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
- assertNull authzInfo.stringPermissions
- assertNull authzInfo.roles
- }
-
- @Test
- void testRememberMeNewAttributeName() {
- CasRealm casRealm = createCasRealm();
- casRealm.rememberMeAttributeName = "rme"
- CasToken casToken = new CasToken('$=defaultId|rme=true');
- AuthenticationInfo authcInfo = casRealm.doGetAuthenticationInfo(casToken);
- def principals = authcInfo.principals
- assertEquals "defaultId", principals.primaryPrincipal
- def attributes = principals.oneByType(Map)
- assertEquals "true", attributes[casRealm.rememberMeAttributeName]
- assertTrue casToken.rememberMe
- AuthorizationInfo authzInfo = casRealm.doGetAuthorizationInfo(principals);
- assertNull authzInfo.stringPermissions
- assertNull authzInfo.roles
- }
-
-}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
deleted file mode 100644
index 471111e..0000000
--- a/support/cas/src/test/groovy/org/apache/shiro/cas/CasTokenTest.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.cas
-
-import org.junit.Test
-
-import static org.junit.Assert.*
-
-/**
- * Unit tests for the {@link CasToken} implementation.
- *
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-class CasTokenTest {
-
- @Test
- void testPrincipal() {
- CasToken casToken = new CasToken("fakeTicket")
- assertNull casToken.principal
- casToken.userId = "myUserId"
- assertEquals "myUserId", casToken.principal
- }
-
- @Test
- void testCredentials() {
- CasToken casToken = new CasToken("fakeTicket")
- assertEquals "fakeTicket", casToken.credentials
- }
-
- @Test
- void testRememberMe() {
- CasToken casToken = new CasToken("fakeTicket")
- assertFalse casToken.rememberMe
- casToken.rememberMe = true
- assertTrue casToken.rememberMe
- }
-}
diff --git a/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy b/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
deleted file mode 100644
index fc46ab2..0000000
--- a/support/cas/src/test/groovy/org/apache/shiro/cas/MockServiceTicketValidator.groovy
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.cas
-
-import org.apache.shiro.util.StringUtils
-import org.jasig.cas.client.authentication.AttributePrincipalImpl
-import org.jasig.cas.client.validation.Assertion
-import org.jasig.cas.client.validation.AssertionImpl
-import org.jasig.cas.client.validation.TicketValidationException
-import org.jasig.cas.client.validation.TicketValidator
-
-/**
- * @since 1.2
- * @see <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>
- * @deprecated replaced with Shiro integration in <a href="https://github.com/bujiio/buji-pac4j">buji-pac4j</a>.
- */
-@Deprecated
-class MockServiceTicketValidator implements TicketValidator {
-
- /**
- * Returns different assertions according to the ticket input. The format of the mock ticket must be :
- * key1=value1,key2=value2,...,keyN=valueN. If keyX is $, valueX is considered to be the name of the principal, otherwise (keyX, valueX)
- * is considered to be an attribute of the principal.
- */
- public Assertion validate(String ticket, String service) throws TicketValidationException {
- String name = null;
- def attributes = [:]
- String[] elements = StringUtils.split(ticket, '|' as char);
- int length = elements.length;
- for (int i = 0; i < length; i++) {
- String[] pair = StringUtils.split(elements[i], '=' as char);
- String key = pair[0].trim();
- String value = pair[1].trim();
- if ('$'.equals(key)) {
- name = value;
- } else {
- attributes.put(key, value);
- }
- }
- AttributePrincipalImpl attributePrincipalImpl = new AttributePrincipalImpl(name, attributes);
- return new AssertionImpl(attributePrincipalImpl, [:]);
-
- }
-}
diff --git a/support/features/src/main/resources/features.xml b/support/features/src/main/resources/features.xml
index 0196f23..d51aaca 100644
--- a/support/features/src/main/resources/features.xml
+++ b/support/features/src/main/resources/features.xml
@@ -40,13 +40,6 @@
<bundle>mvn:org.apache.shiro/shiro-aspectj/${project.version}</bundle>
</feature>
- <feature name="shiro-cas" version="${project.version}">
- <feature prerequisite='true'>wrap</feature>
- <feature version="${project.version}">shiro-web</feature>
- <bundle dependency='true'>wrap:mvn:org.jasig.cas.client/cas-client-core/${cas.client.core.version}$Export-Package=org.jasig*;version=${cas.client.core.version}</bundle>
- <bundle>mvn:org.apache.shiro/shiro-cas/${project.version}</bundle>
- </feature>
-
<feature name="shiro-ehcache" version="${project.version}">
<feature version="${project.version}">shiro-core</feature>
<bundle dependency='true'>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.ehcache/${ehcache.bundle.version}</bundle>
diff --git a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
index df95665..0bda765 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/web/ShiroWebModule.java
@@ -38,6 +38,7 @@
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter;
+import org.apache.shiro.web.filter.authz.IpFilter;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.apache.shiro.web.filter.authz.PortFilter;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
@@ -86,6 +87,8 @@
@SuppressWarnings({"UnusedDeclaration"})
public static final Key<SslFilter> SSL = Key.get(SslFilter.class);
@SuppressWarnings({"UnusedDeclaration"})
+ public static final Key<IpFilter> IP = Key.get(IpFilter.class);
+ @SuppressWarnings({"UnusedDeclaration"})
public static final Key<UserFilter> USER = Key.get(UserFilter.class);
diff --git a/support/pom.xml b/support/pom.xml
index fdc223f..c05cb7d 100644
--- a/support/pom.xml
+++ b/support/pom.xml
@@ -38,7 +38,6 @@
<module>quartz</module>
<module>spring</module>
<module>guice</module>
- <module>cas</module>
<module>spring-boot</module>
<module>servlet-plugin</module>
<module>jaxrs</module>
diff --git a/test-coverage/pom.xml b/test-coverage/pom.xml
index 1abe085..ba97d08 100644
--- a/test-coverage/pom.xml
+++ b/test-coverage/pom.xml
@@ -76,10 +76,6 @@
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-aspectj</artifactId>
</dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-cas</artifactId>
- </dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/IpAddressMatcher.java b/web/src/main/java/org/apache/shiro/web/filter/authz/IpAddressMatcher.java
new file mode 100644
index 0000000..107c80e
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/IpAddressMatcher.java
@@ -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 2.0
+ */
+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);
+ }
+ }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/IpFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/IpFilter.java
new file mode 100644
index 0000000..c5bd4a9
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/IpFilter.java
@@ -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 2.0
+ */
+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;
+ }
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/IpSource.java b/web/src/main/java/org/apache/shiro/web/filter/authz/IpSource.java
new file mode 100644
index 0000000..7b2f626
--- /dev/null
+++ b/web/src/main/java/org/apache/shiro/web/filter/authz/IpSource.java
@@ -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 2.0
+ */
+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();
+}
diff --git a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
index 036f62f..a023feb 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java
@@ -41,6 +41,7 @@
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
authcBearer(BearerHttpAuthenticationFilter.class),
+ ip(IpFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
diff --git a/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java b/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
index 0c777ac..798bf40 100644
--- a/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
+++ b/web/src/main/java/org/apache/shiro/web/mgt/CookieRememberMeManager.java
@@ -212,9 +212,21 @@
if (log.isTraceEnabled()) {
log.trace("Acquired Base64 encoded identity [" + base64 + "]");
}
- byte[] decoded = Base64.decode(base64);
+ byte[] decoded;
+ try {
+ decoded = Base64.decode(base64);
+ } catch (RuntimeException rtEx) {
+ /*
+ * https://issues.apache.org/jira/browse/SHIRO-766:
+ * If the base64 string cannot be decoded, just assume there is no valid cookie value.
+ * */
+ getCookie().removeFrom(request, response);
+ log.warn("Unable to decode existing base64 encoded entity: [" + base64 + "].", rtEx);
+ return null;
+ }
+
if (log.isTraceEnabled()) {
- log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes.");
+ log.trace("Base64 decoded byte array length: " + decoded.length + " bytes.");
}
return decoded;
} else {
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authz/IpAddressMatcherTests.java b/web/src/test/java/org/apache/shiro/web/filter/authz/IpAddressMatcherTests.java
new file mode 100644
index 0000000..ad87303
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authz/IpAddressMatcherTests.java
@@ -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 2.0
+ */
+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"));
+ }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/filter/authz/IpFilterTest.java b/web/src/test/java/org/apache/shiro/web/filter/authz/IpFilterTest.java
new file mode 100644
index 0000000..c5def24
--- /dev/null
+++ b/web/src/test/java/org/apache/shiro/web/filter/authz/IpFilterTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.junit.Test;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Test cases for the {@link AuthorizationFilter} class.
+ * @since 2.0
+ */
+public class IpFilterTest {
+
+ @Test
+ public void accessShouldBeDeniedByDefault() throws Exception {
+ IpFilter filter = new IpFilter();
+ HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+ expect(request.getRemoteAddr()).andReturn("192.168.42.42");
+ replay(request);
+ assertFalse(filter.isAccessAllowed(request, null, null));
+ verify(request);
+ }
+
+ @Test
+ public void accessShouldBeDeniedWhenNotInTheAllowedSet() throws Exception {
+ IpFilter filter = new IpFilter();
+ filter.setAuthorizedIps("192.168.33/24");
+ HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+ expect(request.getRemoteAddr()).andReturn("192.168.42.42");
+ replay(request);
+ assertFalse(filter.isAccessAllowed(request, null, null));
+ verify(request);
+ }
+
+ @Test
+ public void accessShouldBeGrantedToIpsInTheAllowedSet() throws Exception {
+ IpFilter filter = new IpFilter();
+ filter.setAuthorizedIps("192.168.32/24 192.168.33/24 192.168.34/24");
+ HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+ expect(request.getRemoteAddr()).andReturn("192.168.33.44");
+ replay(request);
+ assertFalse(filter.isAccessAllowed(request, null, null));
+ verify(request);
+ }
+
+ @Test
+ public void deniedTakesPrecedenceOverAllowed() throws Exception {
+ IpFilter filter = new IpFilter();
+ filter.setAuthorizedIps("192.168.0.0/16");
+ filter.setDeniedIps("192.168.33.0/24");
+ HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+ expect(request.getRemoteAddr()).andReturn("192.168.33.44");
+ replay(request);
+ assertFalse(filter.isAccessAllowed(request, null, null));
+ verify(request);
+ }
+
+ @Test
+ public void willBlockAndAllowBasedOnIpSource() throws Exception {
+ IpSource source = new IpSource() {
+ public Collection<String> getAuthorizedIps() {
+ return Collections.singleton("192.168.0.0/16");
+ }
+ public Collection<String> getDeniedIps() {
+ return Collections.singleton("192.168.33.0/24");
+ }
+ };
+ IpFilter filter = new IpFilter();
+ filter.setIpSource(source);
+ HttpServletRequest request = createNiceMock(HttpServletRequest.class);
+ expect(request.getRemoteAddr()).andReturn("192.168.33.44");
+ replay(request);
+ assertFalse(filter.isAccessAllowed(request, null, null));
+ verify(request);
+ }
+}
diff --git a/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java b/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
index c4d0963..3b0cd21 100644
--- a/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
+++ b/web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java
@@ -35,6 +35,8 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.util.UUID;
+
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
@@ -211,7 +213,7 @@
CookieRememberMeManager mgr = new CookieRememberMeManager();
try {
mgr.getRememberedPrincipals(context);
- } catch (CryptoException expected) {
+ } catch (IllegalArgumentException expected) {
return;
}
fail("CryptoException was expected to be thrown");
@@ -244,4 +246,30 @@
verify(mockResponse);
verify(cookie);
}
+
+ @Test
+ public void shouldIgnoreInvalidCookieValues() {
+ // given
+ HttpServletRequest mockRequest = createMock(HttpServletRequest.class);
+ HttpServletResponse mockResponse = createMock(HttpServletResponse.class);
+ WebSubjectContext context = new DefaultWebSubjectContext();
+ context.setServletRequest(mockRequest);
+ context.setServletResponse(mockResponse);
+
+ CookieRememberMeManager mgr = new CookieRememberMeManager();
+ Cookie[] cookies = new Cookie[]{
+ new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, UUID.randomUUID().toString() + "%%ldapRealm")
+ };
+
+ expect(mockRequest.getAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY)).andReturn(null);
+ expect(mockRequest.getContextPath()).andReturn(null);
+ expect(mockRequest.getCookies()).andReturn(cookies);
+ replay(mockRequest);
+
+ // when
+ final byte[] rememberedSerializedIdentity = mgr.getRememberedSerializedIdentity(context);
+
+ // then
+ assertNull("should ignore invalid cookie values", rememberedSerializedIdentity);
+ }
}