1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.shardingsphere.proxy.frontend.postgresql.authentication;
19
20 import com.google.common.base.Strings;
21 import io.netty.channel.ChannelHandlerContext;
22 import io.netty.handler.ssl.SslHandler;
23 import org.apache.shardingsphere.authority.checker.AuthorityChecker;
24 import org.apache.shardingsphere.authority.rule.AuthorityRule;
25 import org.apache.shardingsphere.db.protocol.constant.CommonConstants;
26 import org.apache.shardingsphere.db.protocol.constant.DatabaseProtocolServerInfo;
27 import org.apache.shardingsphere.db.protocol.payload.PacketPayload;
28 import org.apache.shardingsphere.db.protocol.postgresql.constant.PostgreSQLAuthenticationMethod;
29 import org.apache.shardingsphere.db.protocol.postgresql.packet.generic.PostgreSQLReadyForQueryPacket;
30 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.PostgreSQLAuthenticationOKPacket;
31 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.PostgreSQLComStartupPacket;
32 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.PostgreSQLParameterStatusPacket;
33 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.PostgreSQLPasswordMessagePacket;
34 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.PostgreSQLRandomGenerator;
35 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.PostgreSQLSSLUnwillingPacket;
36 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.PostgreSQLSSLWillingPacket;
37 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.authentication.PostgreSQLMD5PasswordAuthenticationPacket;
38 import org.apache.shardingsphere.db.protocol.postgresql.packet.handshake.authentication.PostgreSQLPasswordAuthenticationPacket;
39 import org.apache.shardingsphere.db.protocol.postgresql.packet.identifier.PostgreSQLIdentifierPacket;
40 import org.apache.shardingsphere.db.protocol.postgresql.packet.identifier.PostgreSQLMessagePacketType;
41 import org.apache.shardingsphere.db.protocol.postgresql.payload.PostgreSQLPacketPayload;
42 import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
43 import org.apache.shardingsphere.infra.exception.core.ShardingSpherePreconditions;
44 import org.apache.shardingsphere.infra.exception.dialect.exception.syntax.database.UnknownDatabaseException;
45 import org.apache.shardingsphere.infra.exception.postgresql.exception.authority.EmptyUsernameException;
46 import org.apache.shardingsphere.infra.exception.postgresql.exception.authority.InvalidPasswordException;
47 import org.apache.shardingsphere.infra.exception.postgresql.exception.authority.PrivilegeNotGrantedException;
48 import org.apache.shardingsphere.infra.exception.postgresql.exception.authority.UnknownUsernameException;
49 import org.apache.shardingsphere.infra.exception.postgresql.exception.protocol.ProtocolViolationException;
50 import org.apache.shardingsphere.infra.metadata.user.Grantee;
51 import org.apache.shardingsphere.infra.metadata.user.ShardingSphereUser;
52 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
53 import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
54 import org.apache.shardingsphere.proxy.backend.postgresql.handler.admin.executor.variable.charset.PostgreSQLCharacterSets;
55 import org.apache.shardingsphere.proxy.frontend.authentication.AuthenticationEngine;
56 import org.apache.shardingsphere.authentication.result.AuthenticationResult;
57 import org.apache.shardingsphere.authentication.result.AuthenticationResultBuilder;
58 import org.apache.shardingsphere.authentication.Authenticator;
59 import org.apache.shardingsphere.authentication.AuthenticatorFactory;
60 import org.apache.shardingsphere.proxy.frontend.connection.ConnectionIdGenerator;
61 import org.apache.shardingsphere.proxy.frontend.postgresql.authentication.authenticator.PostgreSQLAuthenticatorType;
62 import org.apache.shardingsphere.proxy.frontend.ssl.ProxySSLContext;
63
64 import java.util.Optional;
65
66
67
68
69 public final class PostgreSQLAuthenticationEngine implements AuthenticationEngine {
70
71 private static final int SSL_REQUEST_PAYLOAD_LENGTH = 8;
72
73 private static final int SSL_REQUEST_CODE = (1234 << 16) + 5679;
74
75 private boolean startupMessageReceived;
76
77 private String clientEncoding;
78
79 private byte[] md5Salt;
80
81 private AuthenticationResult currentAuthResult;
82
83 @Override
84 public int handshake(final ChannelHandlerContext context) {
85 return ConnectionIdGenerator.getInstance().nextId();
86 }
87
88 @Override
89 public AuthenticationResult authenticate(final ChannelHandlerContext context, final PacketPayload payload) {
90 if (SSL_REQUEST_PAYLOAD_LENGTH == payload.getByteBuf().markReaderIndex().readInt() && SSL_REQUEST_CODE == payload.getByteBuf().readInt()) {
91 if (ProxySSLContext.getInstance().isSSLEnabled()) {
92 SslHandler sslHandler = new SslHandler(ProxySSLContext.getInstance().newSSLEngine(context.alloc()), true);
93 context.pipeline().addFirst(SslHandler.class.getSimpleName(), sslHandler);
94 context.writeAndFlush(new PostgreSQLSSLWillingPacket());
95 } else {
96 context.writeAndFlush(new PostgreSQLSSLUnwillingPacket());
97 }
98 return AuthenticationResultBuilder.continued();
99 }
100 payload.getByteBuf().resetReaderIndex();
101 AuthorityRule rule = ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData().getGlobalRuleMetaData().getSingleRule(AuthorityRule.class);
102 return startupMessageReceived ? processPasswordMessage(context, (PostgreSQLPacketPayload) payload, rule) : processStartupMessage(context, (PostgreSQLPacketPayload) payload, rule);
103 }
104
105 private AuthenticationResult processPasswordMessage(final ChannelHandlerContext context, final PostgreSQLPacketPayload payload, final AuthorityRule rule) {
106 char messageType = (char) payload.readInt1();
107 ShardingSpherePreconditions.checkState(PostgreSQLMessagePacketType.PASSWORD_MESSAGE.getValue() == messageType,
108 () -> new ProtocolViolationException("password", Character.toString(messageType)));
109 PostgreSQLPasswordMessagePacket passwordMessagePacket = new PostgreSQLPasswordMessagePacket(payload);
110 login(currentAuthResult.getDatabase(), currentAuthResult.getUsername(), md5Salt, passwordMessagePacket.getDigest(), rule);
111
112 context.write(new PostgreSQLAuthenticationOKPacket());
113 context.write(new PostgreSQLParameterStatusPacket("server_version",
114 DatabaseProtocolServerInfo.getProtocolVersion(currentAuthResult.getDatabase(), TypedSPILoader.getService(DatabaseType.class, "PostgreSQL"))));
115 context.write(new PostgreSQLParameterStatusPacket("client_encoding", clientEncoding));
116 context.write(new PostgreSQLParameterStatusPacket("server_encoding", "UTF8"));
117 context.write(new PostgreSQLParameterStatusPacket("integer_datetimes", "on"));
118 context.write(new PostgreSQLParameterStatusPacket("standard_conforming_strings", "on"));
119 context.writeAndFlush(PostgreSQLReadyForQueryPacket.NOT_IN_TRANSACTION);
120 return AuthenticationResultBuilder.finished(currentAuthResult.getUsername(), "", currentAuthResult.getDatabase());
121 }
122
123 private void login(final String databaseName, final String username, final byte[] md5Salt, final String digest, final AuthorityRule rule) {
124 ShardingSpherePreconditions.checkState(Strings.isNullOrEmpty(databaseName) || ProxyContext.getInstance().databaseExists(databaseName), () -> new UnknownDatabaseException(databaseName));
125 Grantee grantee = new Grantee(username, "%");
126 Optional<ShardingSphereUser> user = rule.findUser(grantee);
127 ShardingSpherePreconditions.checkState(user.isPresent(), () -> new UnknownUsernameException(username));
128 ShardingSpherePreconditions.checkState(new AuthenticatorFactory<>(PostgreSQLAuthenticatorType.class, rule).newInstance(user.get()).authenticate(user.get(), new Object[]{digest, md5Salt}),
129 () -> new InvalidPasswordException(username));
130 ShardingSpherePreconditions.checkState(null == databaseName || new AuthorityChecker(rule, grantee).isAuthorized(databaseName), () -> new PrivilegeNotGrantedException(username, databaseName));
131 }
132
133 private AuthenticationResult processStartupMessage(final ChannelHandlerContext context, final PostgreSQLPacketPayload payload, final AuthorityRule rule) {
134 startupMessageReceived = true;
135 PostgreSQLComStartupPacket startupPacket = new PostgreSQLComStartupPacket(payload);
136 clientEncoding = startupPacket.getClientEncoding();
137 context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).set(PostgreSQLCharacterSets.findCharacterSet(clientEncoding));
138 String username = startupPacket.getUsername();
139 ShardingSpherePreconditions.checkNotEmpty(username, EmptyUsernameException::new);
140 context.writeAndFlush(getIdentifierPacket(username, rule));
141 currentAuthResult = AuthenticationResultBuilder.continued(username, "", startupPacket.getDatabase());
142 return currentAuthResult;
143 }
144
145 private PostgreSQLIdentifierPacket getIdentifierPacket(final String username, final AuthorityRule rule) {
146 Optional<Authenticator> authenticator = rule.findUser(new Grantee(username, "")).map(optional -> new AuthenticatorFactory<>(PostgreSQLAuthenticatorType.class, rule).newInstance(optional));
147 if (authenticator.isPresent() && PostgreSQLAuthenticationMethod.PASSWORD.getMethodName().equals(authenticator.get().getAuthenticationMethodName())) {
148 return new PostgreSQLPasswordAuthenticationPacket();
149 }
150 md5Salt = PostgreSQLRandomGenerator.getInstance().generateRandomBytes(4);
151 return new PostgreSQLMD5PasswordAuthenticationPacket(md5Salt);
152 }
153 }