Skip to content

Commit 8e6b560

Browse files
committed
provides first draft implementation of AuthMetadataFlyweight
right now it supports encoding only Signed-off-by: Oleh Dokuka <[email protected]>
1 parent ce9bbae commit 8e6b560

File tree

4 files changed

+721
-0
lines changed

4 files changed

+721
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package io.rsocket.metadata.security;
2+
3+
import io.netty.buffer.ByteBuf;
4+
import io.netty.buffer.ByteBufAllocator;
5+
import io.netty.buffer.ByteBufUtil;
6+
import io.rsocket.buffer.TupleByteBuf;
7+
import io.rsocket.util.CharByteBufUtil;
8+
9+
public class AuthMetadataFlyweight {
10+
11+
static final int STREAM_METADATA_KNOWN_MASK = 0x80; // 1000 0000
12+
static final byte STREAM_METADATA_LENGTH_MASK = 0x7F; // 0111 1111
13+
14+
static final int USERNAME_BYTES_LENGTH = 1;
15+
static final int AUTH_TYPE_ID_LENGTH = 1;
16+
17+
private AuthMetadataFlyweight() {}
18+
19+
/**
20+
* Encode a Authentication CompositeMetadata payload using custom authentication type
21+
*
22+
* @throws IllegalArgumentException if customAuthType is non US_ASCII string if customAuthType is
23+
* empty string if customAuthType is has length greater than 128 bytes
24+
* @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed.
25+
* @param customAuthType the custom mime type to encode.
26+
* @param metadata the metadata value to encode.
27+
*/
28+
public static ByteBuf encodeMetadata(
29+
ByteBufAllocator allocator, String customAuthType, ByteBuf metadata) {
30+
31+
int actualASCIILength = ByteBufUtil.utf8Bytes(customAuthType);
32+
if (actualASCIILength != customAuthType.length()) {
33+
metadata.release();
34+
throw new IllegalArgumentException("custom auth type must be US_ASCII characters only");
35+
}
36+
if (actualASCIILength < 1 || actualASCIILength > 128) {
37+
metadata.release();
38+
throw new IllegalArgumentException(
39+
"custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128");
40+
}
41+
42+
int capacity = 1 + actualASCIILength;
43+
ByteBuf headerBuffer = allocator.buffer(capacity, capacity);
44+
// encoded length is one less than actual length, since 0 is never a valid length, which gives
45+
// wider representation range
46+
headerBuffer.writeByte(actualASCIILength - 1);
47+
48+
ByteBufUtil.reserveAndWriteUtf8(headerBuffer, customAuthType, actualASCIILength);
49+
50+
return TupleByteBuf.of(allocator, headerBuffer, metadata);
51+
}
52+
53+
/**
54+
* Encode a Authentication CompositeMetadata payload using custom authentication type
55+
*
56+
* @throws IllegalArgumentException if customAuthType is non US_ASCII string if customAuthType is
57+
* empty string if customAuthType is has length greater than 128 bytes
58+
* @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed.
59+
* @param authType the custom mime type to encode.
60+
* @param metadata the metadata value to encode.
61+
*/
62+
public static ByteBuf encodeMetadata(
63+
ByteBufAllocator allocator, WellKnownAuthType authType, ByteBuf metadata) {
64+
65+
if (authType == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE
66+
|| authType == WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE) {
67+
metadata.release();
68+
throw new IllegalArgumentException("only allowed AuthType should be used");
69+
}
70+
71+
int capacity = AUTH_TYPE_ID_LENGTH;
72+
ByteBuf headerBuffer =
73+
allocator
74+
.buffer(capacity, capacity)
75+
.writeByte(authType.getIdentifier() | STREAM_METADATA_KNOWN_MASK);
76+
77+
return TupleByteBuf.of(allocator, headerBuffer, metadata);
78+
}
79+
80+
/**
81+
* Encode a Authentication CompositeMetadata payload using Simple Authentication format
82+
*
83+
* @throws IllegalArgumentException if username length is greater than 128
84+
* @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed.
85+
* @param username the char sequence which represents user name.
86+
* @param password the char sequence which represents user password.
87+
*/
88+
public static ByteBuf encodeSimpleMetadata(
89+
ByteBufAllocator allocator, char[] username, char[] password) {
90+
91+
int usernameLength = CharByteBufUtil.utf8Bytes(username);
92+
if (usernameLength > 128) {
93+
throw new IllegalArgumentException(
94+
"Username should be shorter than or equal to 128 bytes length in UTF-8 encoding");
95+
}
96+
97+
int passwordLength = CharByteBufUtil.utf8Bytes(password);
98+
int capacity = AUTH_TYPE_ID_LENGTH + USERNAME_BYTES_LENGTH + usernameLength + passwordLength;
99+
final ByteBuf buffer =
100+
allocator
101+
.buffer(capacity, capacity)
102+
.writeByte(WellKnownAuthType.SIMPLE.getIdentifier() | STREAM_METADATA_KNOWN_MASK)
103+
.writeByte(usernameLength);
104+
105+
CharByteBufUtil.writeUtf8(buffer, username);
106+
CharByteBufUtil.writeUtf8(buffer, password);
107+
108+
return buffer;
109+
}
110+
111+
/**
112+
* Encode a Authentication CompositeMetadata payload using Bearer Authentication format
113+
*
114+
* @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed.
115+
* @param token the char sequence which represents BEARER token.
116+
*/
117+
public static ByteBuf encodeBearerMetadata(ByteBufAllocator allocator, char[] token) {
118+
119+
int tokenLength = CharByteBufUtil.utf8Bytes(token);
120+
int capacity = AUTH_TYPE_ID_LENGTH + tokenLength;
121+
final ByteBuf buffer =
122+
allocator
123+
.buffer(capacity, capacity)
124+
.writeByte(WellKnownAuthType.BEARER.getIdentifier() | STREAM_METADATA_KNOWN_MASK);
125+
126+
CharByteBufUtil.writeUtf8(buffer, token);
127+
128+
return buffer;
129+
}
130+
131+
/**
132+
* Encode a new Authentication Metadata payload information, first verifying if the passed {@link
133+
* String} matches a {@link WellKnownAuthType} (in which case it will be encoded in a compressed
134+
* fashion using the mime id of that type).
135+
*
136+
* <p>Prefer using {@link #encodeMetadata(ByteBufAllocator, String, ByteBuf)} if you already know
137+
* that the mime type is not a {@link WellKnownAuthType}.
138+
*
139+
* @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed.
140+
* @param authType the mime type to encode, as a {@link String}. well known mime types are
141+
* compressed.
142+
* @param metadata the metadata value to encode.
143+
* @see #encodeMetadata(ByteBufAllocator, WellKnownAuthType, ByteBuf)
144+
* @see #encodeMetadata(ByteBufAllocator, String, ByteBuf)
145+
*/
146+
public static ByteBuf encodeMetadataWithCompression(
147+
ByteBufAllocator allocator, String authType, ByteBuf metadata) {
148+
WellKnownAuthType wkn = WellKnownAuthType.fromString(authType);
149+
if (wkn == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE) {
150+
return AuthMetadataFlyweight.encodeMetadata(allocator, authType, metadata);
151+
} else {
152+
return AuthMetadataFlyweight.encodeMetadata(allocator, wkn, metadata);
153+
}
154+
}
155+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright 2015-2018 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+
* http://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 io.rsocket.metadata.security;
18+
19+
import java.util.Arrays;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
/**
24+
* Enumeration of Well Known Auth Types, as defined in the eponymous extension. Such auth types are
25+
* used in composite metadata (which can include routing and/or tracing metadata). Per
26+
* specification, identifiers are between 0 and 127 (inclusive).
27+
*/
28+
public enum WellKnownAuthType {
29+
UNPARSEABLE_AUTH_TYPE("UNPARSEABLE_AUTH_TYPE_DO_NOT_USE", (byte) -2),
30+
UNKNOWN_RESERVED_AUTH_TYPE("UNKNOWN_YET_RESERVED_DO_NOT_USE", (byte) -1),
31+
32+
SIMPLE("simple", (byte) 0x00),
33+
BEARER("bearer", (byte) 0x01);
34+
// ... reserved for future use ...
35+
36+
static final WellKnownAuthType[] TYPES_BY_AUTH_ID;
37+
static final Map<String, WellKnownAuthType> TYPES_BY_AUTH_STRING;
38+
39+
static {
40+
// precompute an array of all valid auth ids, filling the blanks with the RESERVED enum
41+
TYPES_BY_AUTH_ID = new WellKnownAuthType[128]; // 0-127 inclusive
42+
Arrays.fill(TYPES_BY_AUTH_ID, UNKNOWN_RESERVED_AUTH_TYPE);
43+
// also prepare a Map of the types by auth string
44+
TYPES_BY_AUTH_STRING = new HashMap<>(128);
45+
46+
for (WellKnownAuthType value : values()) {
47+
if (value.getIdentifier() >= 0) {
48+
TYPES_BY_AUTH_ID[value.getIdentifier()] = value;
49+
TYPES_BY_AUTH_STRING.put(value.getString(), value);
50+
}
51+
}
52+
}
53+
54+
private final byte identifier;
55+
private final String str;
56+
57+
WellKnownAuthType(String str, byte identifier) {
58+
this.str = str;
59+
this.identifier = identifier;
60+
}
61+
62+
/**
63+
* Find the {@link WellKnownAuthType} for the given identifier (as an {@code int}). Valid
64+
* identifiers are defined to be integers between 0 and 127, inclusive. Identifiers outside of
65+
* this range will produce the {@link #UNPARSEABLE_AUTH_TYPE}. Additionally, some identifiers in
66+
* that range are still only reserved and don't have a type associated yet: this method returns
67+
* the {@link #UNKNOWN_RESERVED_AUTH_TYPE} when passing such an identifier, which lets call sites
68+
* potentially detect this and keep the original representation when transmitting the associated
69+
* metadata buffer.
70+
*
71+
* @param id the looked up identifier
72+
* @return the {@link WellKnownAuthType}, or {@link #UNKNOWN_RESERVED_AUTH_TYPE} if the id is out
73+
* of the specification's range, or {@link #UNKNOWN_RESERVED_AUTH_TYPE} if the id is one that
74+
* is merely reserved but unknown to this implementation.
75+
*/
76+
public static WellKnownAuthType fromIdentifier(int id) {
77+
if (id < 0x00 || id > 0x7F) {
78+
return UNPARSEABLE_AUTH_TYPE;
79+
}
80+
return TYPES_BY_AUTH_ID[id];
81+
}
82+
83+
/**
84+
* Find the {@link WellKnownAuthType} for the given {@link String} representation. If the
85+
* representation is {@code null} or doesn't match a {@link WellKnownAuthType}, the {@link
86+
* #UNPARSEABLE_AUTH_TYPE} is returned.
87+
*
88+
* @param authType the looked up auth type
89+
* @return the matching {@link WellKnownAuthType}, or {@link #UNPARSEABLE_AUTH_TYPE} if none
90+
* matches
91+
*/
92+
public static WellKnownAuthType fromString(String authType) {
93+
if (authType == null) throw new IllegalArgumentException("type must be non-null");
94+
95+
// force UNPARSEABLE if by chance UNKNOWN_RESERVED_AUTH_TYPE's text has been used
96+
if (authType.equals(UNKNOWN_RESERVED_AUTH_TYPE.str)) {
97+
return UNPARSEABLE_AUTH_TYPE;
98+
}
99+
100+
return TYPES_BY_AUTH_STRING.getOrDefault(authType, UNPARSEABLE_AUTH_TYPE);
101+
}
102+
103+
/** @return the byte identifier of the auth type, guaranteed to be positive or zero. */
104+
public byte getIdentifier() {
105+
return identifier;
106+
}
107+
108+
/**
109+
* @return the auth type represented as a {@link String}, which is made of US_ASCII compatible
110+
* characters only
111+
*/
112+
public String getString() {
113+
return str;
114+
}
115+
116+
/** @see #getString() */
117+
@Override
118+
public String toString() {
119+
return str;
120+
}
121+
}

0 commit comments

Comments
 (0)