1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.frontend.support;
20
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.nio.charset.StandardCharsets;
24 import java.security.MessageDigest;
25 import java.security.NoSuchAlgorithmException;
26 import java.util.Arrays;
27 import java.util.Base64;
28 import java.util.Objects;
29 import java.util.stream.Collectors;
30 import java.util.stream.Stream;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class MCRSecureTokenV2 {
50
51 private String contentPath, ipAddress, sharedSecret, hash;
52
53 private String[] queryParameters;
54
55 public MCRSecureTokenV2(String contentPath, String ipAddress, String sharedSecret, String... queryParameters) {
56 this.contentPath = Objects.requireNonNull(contentPath, "'contentPath' may not be null");
57 this.ipAddress = Objects.requireNonNull(ipAddress, "'ipAddress' may not be null");
58 this.sharedSecret = Objects.requireNonNull(sharedSecret, "'sharedSecret' may not be null");
59 this.queryParameters = queryParameters;
60 try {
61 this.contentPath = new URI(null, null, this.contentPath, null).getRawPath();
62 } catch (URISyntaxException e) {
63 throw new RuntimeException(e);
64 }
65 buildHash();
66 }
67
68 private void buildHash() {
69 String forHashing = Stream.concat(Stream.of(ipAddress, sharedSecret),
70 Arrays.stream(queryParameters).filter(Objects::nonNull))
71 .sorted()
72 .collect(Collectors.joining("&", contentPath + "?", ""));
73 MessageDigest digest;
74 try {
75 digest = MessageDigest.getInstance("SHA-256");
76 } catch (NoSuchAlgorithmException e) {
77 throw new RuntimeException(e);
78 }
79 digest.update(URI.create(forHashing).toASCIIString().getBytes(StandardCharsets.US_ASCII));
80 byte[] sha256 = digest.digest();
81 hash = Base64.getEncoder()
82 .encodeToString(sha256)
83 .chars()
84 .map(x -> {
85 switch (x) {
86 case '+':
87 return '-';
88 case '/':
89 return '_';
90 default:
91 return x;
92 }
93 })
94 .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
95 .toString();
96 }
97
98 public String getHash() {
99 return hash;
100 }
101
102
103
104
105 public URI toURI(String baseURL, String hashParameterName) throws URISyntaxException {
106 return toURI(baseURL, "", hashParameterName);
107 }
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 public URI toURI(String baseURL, String suffix, String hashParameterName) throws URISyntaxException {
125 Objects.requireNonNull(suffix, "'suffix' may not be null");
126 Objects.requireNonNull(hashParameterName, "'hashParameterName' may not be null");
127 if (hashParameterName.isEmpty()) {
128 throw new IllegalArgumentException("'hashParameterName' may not be empty");
129 }
130 URI context = new URI(baseURL);
131 return context.resolve(Stream
132 .concat(Arrays.stream(queryParameters).filter(Objects::nonNull), Stream.of(hashParameterName + "=" + hash))
133 .collect(Collectors.joining("&", baseURL + contentPath + suffix + "?", "")));
134 }
135
136 @Override
137 public int hashCode() {
138 return getHash().hashCode();
139 }
140
141 @Override
142 public boolean equals(Object obj) {
143 if (this == obj) {
144 return true;
145 }
146 if (obj == null) {
147 return false;
148 }
149 if (getClass() != obj.getClass()) {
150 return false;
151 }
152 MCRSecureTokenV2 other = (MCRSecureTokenV2) obj;
153 if (!hash.equals(other.hash)) {
154 return false;
155 }
156 if (!contentPath.equals(other.contentPath)) {
157 return false;
158 }
159 if (!ipAddress.equals(other.ipAddress)) {
160 return false;
161 }
162 if (!sharedSecret.equals(other.sharedSecret)) {
163 return false;
164 }
165 return Arrays.equals(queryParameters, other.queryParameters);
166 }
167
168 }