Skip to content

Commit c6d6bfd

Browse files
committed
Move OpenSAML 4 Support to Separate Source Directory
Issue gh-11658
1 parent 1be596b commit c6d6bfd

File tree

41 files changed

+250
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+250
-3
lines changed

saml2/saml2-service-provider/spring-security-saml2-service-provider.gradle

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
apply plugin: 'io.spring.convention.spring-module'
22

3+
configurations {
4+
opensaml4Main { extendsFrom(optional, provided) }
5+
opensaml4Test { extendsFrom(opensaml4Main, tests) }
6+
}
7+
8+
sourceSets {
9+
opensaml4Main {
10+
java {
11+
compileClasspath += main.output + configurations.opensaml4Main
12+
srcDir 'src/opensaml4Main/java'
13+
}
14+
}
15+
opensaml4Test {
16+
java {
17+
compileClasspath += main.output + test.output + opensaml4Main.output + configurations.opensaml4Test
18+
srcDir 'src/opensaml4Test/java'
19+
}
20+
}
21+
}
22+
323
sourceSets.configureEach { set ->
424
if (!set.name.containsIgnoreCase("main")) {
525
return
@@ -76,3 +96,34 @@ dependencies {
7696
testImplementation "org.mockito:mockito-junit-jupiter"
7797
testImplementation "org.springframework:spring-test"
7898
}
99+
100+
jar {
101+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
102+
from sourceSets.opensaml4Main.output
103+
}
104+
105+
sourcesJar {
106+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
107+
from sourceSets.opensaml4Main.allJava
108+
}
109+
110+
testJar {
111+
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
112+
from sourceSets.opensaml4Test.output
113+
}
114+
115+
javadoc {
116+
classpath += sourceSets.opensaml4Main.runtimeClasspath
117+
source += sourceSets.opensaml4Main.allJava
118+
}
119+
120+
tasks.register("opensaml4Test", Test) {
121+
useJUnitPlatform()
122+
classpath += sourceSets.opensaml4Test.output
123+
}
124+
125+
126+
tasks.named("test") {
127+
dependsOn opensaml4Test
128+
}
129+
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.saml2.provider.service.authentication.logout;
18+
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.IOException;
21+
import java.nio.charset.StandardCharsets;
22+
import java.util.Arrays;
23+
import java.util.Base64;
24+
import java.util.zip.Deflater;
25+
import java.util.zip.DeflaterOutputStream;
26+
import java.util.zip.Inflater;
27+
import java.util.zip.InflaterOutputStream;
28+
29+
import org.springframework.security.saml2.Saml2Exception;
30+
31+
/**
32+
* Utility methods for working with serialized SAML messages.
33+
*
34+
* For internal use only.
35+
*
36+
* @author Josh Cummings
37+
*/
38+
final class Saml2Utils {
39+
40+
private Saml2Utils() {
41+
}
42+
43+
static String samlEncode(byte[] b) {
44+
return Base64.getEncoder().encodeToString(b);
45+
}
46+
47+
static byte[] samlDecode(String s) {
48+
return Base64.getMimeDecoder().decode(s);
49+
}
50+
51+
static byte[] samlDeflate(String s) {
52+
try {
53+
ByteArrayOutputStream b = new ByteArrayOutputStream();
54+
DeflaterOutputStream deflater = new DeflaterOutputStream(b, new Deflater(Deflater.DEFLATED, true));
55+
deflater.write(s.getBytes(StandardCharsets.UTF_8));
56+
deflater.finish();
57+
return b.toByteArray();
58+
}
59+
catch (IOException ex) {
60+
throw new Saml2Exception("Unable to deflate string", ex);
61+
}
62+
}
63+
64+
static String samlInflate(byte[] b) {
65+
try {
66+
ByteArrayOutputStream out = new ByteArrayOutputStream();
67+
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
68+
iout.write(b);
69+
iout.finish();
70+
return new String(out.toByteArray(), StandardCharsets.UTF_8);
71+
}
72+
catch (IOException ex) {
73+
throw new Saml2Exception("Unable to inflate string", ex);
74+
}
75+
}
76+
77+
static EncodingConfigurer withDecoded(String decoded) {
78+
return new EncodingConfigurer(decoded);
79+
}
80+
81+
static DecodingConfigurer withEncoded(String encoded) {
82+
return new DecodingConfigurer(encoded);
83+
}
84+
85+
static final class EncodingConfigurer {
86+
87+
private final String decoded;
88+
89+
private boolean deflate;
90+
91+
private EncodingConfigurer(String decoded) {
92+
this.decoded = decoded;
93+
}
94+
95+
EncodingConfigurer deflate(boolean deflate) {
96+
this.deflate = deflate;
97+
return this;
98+
}
99+
100+
String encode() {
101+
byte[] bytes = (this.deflate) ? Saml2Utils.samlDeflate(this.decoded)
102+
: this.decoded.getBytes(StandardCharsets.UTF_8);
103+
return Saml2Utils.samlEncode(bytes);
104+
}
105+
106+
}
107+
108+
static final class DecodingConfigurer {
109+
110+
private static final Base64Checker BASE_64_CHECKER = new Base64Checker();
111+
112+
private final String encoded;
113+
114+
private boolean inflate;
115+
116+
private boolean requireBase64;
117+
118+
private DecodingConfigurer(String encoded) {
119+
this.encoded = encoded;
120+
}
121+
122+
DecodingConfigurer inflate(boolean inflate) {
123+
this.inflate = inflate;
124+
return this;
125+
}
126+
127+
DecodingConfigurer requireBase64(boolean requireBase64) {
128+
this.requireBase64 = requireBase64;
129+
return this;
130+
}
131+
132+
String decode() {
133+
if (this.requireBase64) {
134+
BASE_64_CHECKER.checkAcceptable(this.encoded);
135+
}
136+
byte[] bytes = Saml2Utils.samlDecode(this.encoded);
137+
return (this.inflate) ? Saml2Utils.samlInflate(bytes) : new String(bytes, StandardCharsets.UTF_8);
138+
}
139+
140+
static class Base64Checker {
141+
142+
private static final int[] values = genValueMapping();
143+
144+
Base64Checker() {
145+
146+
}
147+
148+
private static int[] genValueMapping() {
149+
byte[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
150+
.getBytes(StandardCharsets.ISO_8859_1);
151+
152+
int[] values = new int[256];
153+
Arrays.fill(values, -1);
154+
for (int i = 0; i < alphabet.length; i++) {
155+
values[alphabet[i] & 0xff] = i;
156+
}
157+
return values;
158+
}
159+
160+
boolean isAcceptable(String s) {
161+
int goodChars = 0;
162+
int lastGoodCharVal = -1;
163+
164+
// count number of characters from Base64 alphabet
165+
for (int i = 0; i < s.length(); i++) {
166+
int val = values[0xff & s.charAt(i)];
167+
if (val != -1) {
168+
lastGoodCharVal = val;
169+
goodChars++;
170+
}
171+
}
172+
173+
// in cases of an incomplete final chunk, ensure the unused bits are zero
174+
switch (goodChars % 4) {
175+
case 0:
176+
return true;
177+
case 2:
178+
return (lastGoodCharVal & 0b1111) == 0;
179+
case 3:
180+
return (lastGoodCharVal & 0b11) == 0;
181+
default:
182+
return false;
183+
}
184+
}
185+
186+
void checkAcceptable(String ins) {
187+
if (!isAcceptable(ins)) {
188+
throw new IllegalArgumentException("Failed to decode SAMLResponse");
189+
}
190+
}
191+
192+
}
193+
194+
}
195+
196+
}

0 commit comments

Comments
 (0)