1 package com.atlassian.asap.core.validator;
2
3 import com.atlassian.asap.core.exception.InvalidHeaderException;
4 import com.google.common.collect.ImmutableSet;
5 import org.apache.commons.lang3.StringUtils;
6 import org.apache.commons.lang3.builder.EqualsBuilder;
7 import org.apache.commons.lang3.builder.HashCodeBuilder;
8 import org.apache.commons.lang3.builder.ToStringBuilder;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11
12 import java.util.List;
13 import java.util.Objects;
14 import java.util.Set;
15 import java.util.regex.Pattern;
16 import java.util.stream.Collectors;
17
18 import static org.apache.commons.lang3.StringUtils.isBlank;
19
20
21
22
23
24 public final class ValidatedKeyId {
25
26
27
28
29
30 public static final Pattern PATH_PATTERN = Pattern.compile("^[\\w.\\-\\+/]*$");
31
32 private static final Set<String> PATH_TRAVERSAL_COMPONENTS = ImmutableSet.of(".", "..");
33 private static final Pattern PATH_SPLITTER = Pattern.compile("/");
34
35 private static final Logger logger = LoggerFactory.getLogger(ValidatedKeyId.class);
36
37 private final String keyId;
38
39 private ValidatedKeyId(String validatedKeyId) {
40 this.keyId = Objects.requireNonNull(validatedKeyId);
41 }
42
43
44
45
46
47
48
49
50 public static ValidatedKeyId validate(String unvalidatedKeyId) throws InvalidHeaderException {
51 if (isBlank(unvalidatedKeyId)) {
52 logger.debug("Rejecting absent or blank kid");
53 throw new InvalidHeaderException("The kid header is required");
54 }
55
56
57 List<String> pathComponents = PATH_SPLITTER.splitAsStream(unvalidatedKeyId)
58 .map(StringUtils::trim)
59 .collect(Collectors.toList());
60 if (pathComponents.stream().anyMatch(PATH_TRAVERSAL_COMPONENTS::contains)) {
61 logger.debug("Rejecting kid value {} because it contains path traversal", unvalidatedKeyId);
62 throw new InvalidHeaderException("Path traversal components not allowed in kid");
63 }
64
65
66 if (pathComponents.stream().anyMatch(StringUtils::isBlank)) {
67 logger.debug("Rejecting kid value {} because it is in invalid format", unvalidatedKeyId);
68 throw new InvalidHeaderException("Invalid format of kid");
69 }
70
71
72 if (!PATH_PATTERN.matcher(unvalidatedKeyId).matches()) {
73 logger.debug("Rejecting kid value {} because it contains invalid characters", unvalidatedKeyId);
74 throw new InvalidHeaderException("Invalid character found in kid");
75 }
76
77 return new ValidatedKeyId(unvalidatedKeyId);
78 }
79
80
81
82
83 public String getKeyId() {
84 return keyId;
85 }
86
87 @Override
88 public boolean equals(Object o) {
89 if (this == o) return true;
90
91 if (o == null || getClass() != o.getClass()) return false;
92
93 ValidatedKeyId that = (ValidatedKeyId) o;
94
95 return new EqualsBuilder()
96 .append(keyId, that.keyId)
97 .isEquals();
98 }
99
100 @Override
101 public int hashCode() {
102 return new HashCodeBuilder()
103 .append(keyId)
104 .toHashCode();
105 }
106
107 @Override
108 public String toString() {
109 return new ToStringBuilder(this)
110 .append("keyId", keyId)
111 .toString();
112 }
113 }