blob: 333b68ebcc7b39d01b7bf0de8615eead039b6b3c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.cache.client.internal;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_CLIENT_AUTH_INIT;
import java.util.Properties;
import org.apache.geode.DataSerializer;
import org.apache.geode.InternalGemFireError;
import org.apache.geode.cache.client.ServerOperationException;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.distributed.internal.ServerLocation;
import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
import org.apache.geode.internal.ByteArrayDataInput;
import org.apache.geode.internal.HeapDataOutputStream;
import org.apache.geode.internal.Version;
import org.apache.geode.internal.cache.tier.MessageType;
import org.apache.geode.internal.cache.tier.sockets.Handshake;
import org.apache.geode.internal.cache.tier.sockets.Message;
import org.apache.geode.internal.cache.tier.sockets.Part;
import org.apache.geode.internal.cache.tier.sockets.command.PutUserCredentials;
import org.apache.geode.internal.logging.InternalLogWriter;
import org.apache.geode.security.AuthenticationFailedException;
import org.apache.geode.security.AuthenticationRequiredException;
import org.apache.geode.security.NotAuthorizedException;
/**
* Authenticates this client (or a user) on a server. This op ideally should get executed
* once-per-server.
*
* When multiuser-authentication is set to false, this op gets executed immedialtely after a
* client-to-server connection is established.
*
* When multiuser-authentication is set to true, this op gets executed before the user attempts to
* perform an op whose {@link AbstractOp#needsUserId()} returns true.
*
* @see PutUserCredentials
* @see ProxyCache
* @since GemFire 6.5
*/
public class AuthenticateUserOp {
/**
* Sends the auth credentials to the server. Used in single user mode of authentication.
*
* @param con The connection to use for this operation.
* @param pool The connection pool to use for this operation.
* @return Object unique user-id.
*/
public static Object executeOn(Connection con, ExecutablePool pool) {
AbstractOp op = new AuthenticateUserOpImpl(con);
return pool.executeOn(con, op);
}
/**
* Sends the auth credentials to the server for a particular user. Used in multiple user mode of
* authentication.
*
* @param location The ServerLocation instance whose connection instance will be used to perform
* the operation.
* @param pool The connection pool to use for this operation.
* @return Object unique user-id.
*/
public static Object executeOn(ServerLocation location, ExecutablePool pool,
Properties securityProps) {
AbstractOp op = new AuthenticateUserOpImpl(securityProps);
return pool.executeOn(location, op);
}
private AuthenticateUserOp() {
// no instances allowed
}
static class AuthenticateUserOpImpl extends AbstractOp {
private Properties securityProperties = null;
private boolean needsServerLocation = false;
AuthenticateUserOpImpl(Connection con) {
super(MessageType.USER_CREDENTIAL_MESSAGE, 1);
byte[] credentialBytes;
DistributedMember server = new InternalDistributedMember(con.getSocket().getInetAddress(),
con.getSocket().getPort(), false);
DistributedSystem sys = InternalDistributedSystem.getConnectedInstance();
String authInitMethod = sys.getProperties().getProperty(SECURITY_CLIENT_AUTH_INIT);
Properties tmpSecurityProperties = sys.getSecurityProperties();
// LOG: following passes the DS API LogWriters into the security API
Properties credentials = Handshake.getCredentials(authInitMethod, tmpSecurityProperties,
server, false, (InternalLogWriter) sys.getLogWriter(),
(InternalLogWriter) sys.getSecurityLogWriter());
getMessage().setMessageHasSecurePartFlag();
try (HeapDataOutputStream heapdos = new HeapDataOutputStream(Version.CURRENT)) {
DataSerializer.writeProperties(credentials, heapdos);
credentialBytes = ((ConnectionImpl) con).encryptBytes(heapdos.toByteArray());
} catch (Exception e) {
throw new ServerOperationException(e);
}
getMessage().addBytesPart(credentialBytes);
}
AuthenticateUserOpImpl(Properties securityProps) {
this(securityProps, false);
}
AuthenticateUserOpImpl(Properties securityProps,
boolean needsServer) {
super(MessageType.USER_CREDENTIAL_MESSAGE, 1);
securityProperties = securityProps;
needsServerLocation = needsServer;
getMessage().setMessageHasSecurePartFlag();
}
@Override
protected void sendMessage(Connection cnx) throws Exception {
HeapDataOutputStream hdos = new HeapDataOutputStream(Version.CURRENT);
byte[] secureBytes;
hdos.writeLong(cnx.getConnectionID());
if (securityProperties != null) {
DistributedMember server = new InternalDistributedMember(cnx.getSocket().getInetAddress(),
cnx.getSocket().getPort(), false);
DistributedSystem sys = InternalDistributedSystem.getConnectedInstance();
String authInitMethod = sys.getProperties().getProperty(SECURITY_CLIENT_AUTH_INIT);
Properties credentials = Handshake.getCredentials(authInitMethod, securityProperties,
server, false, (InternalLogWriter) sys.getLogWriter(),
(InternalLogWriter) sys.getSecurityLogWriter());
byte[] credentialBytes;
try (HeapDataOutputStream heapdos = new HeapDataOutputStream(Version.CURRENT)) {
DataSerializer.writeProperties(credentials, heapdos);
credentialBytes = ((ConnectionImpl) cnx).encryptBytes(heapdos.toByteArray());
}
getMessage().addBytesPart(credentialBytes);
}
try {
secureBytes = ((ConnectionImpl) cnx).encryptBytes(hdos.toByteArray());
} finally {
hdos.close();
}
getMessage().setSecurePart(secureBytes);
getMessage().send(false);
}
@Override
public Object attempt(Connection cnx) throws Exception {
if (cnx.getServer().getRequiresCredentials()) {
return super.attempt(cnx);
} else {
return null;
}
}
@Override
protected Object processResponse(Message msg, Connection cnx) throws Exception {
byte[] bytes;
Part part = msg.getPart(0);
final int msgType = msg.getMessageType();
long userId = -1;
if (msgType == MessageType.RESPONSE) {
bytes = (byte[]) part.getObject();
if (bytes.length == 0) {
cnx.getServer().setRequiresCredentials(false);
} else {
cnx.getServer().setRequiresCredentials(true);
byte[] decrypted = ((ConnectionImpl) cnx).decryptBytes(bytes);
ByteArrayDataInput dis = new ByteArrayDataInput(decrypted);
userId = dis.readLong();
}
if (needsServerLocation) {
return new Object[] {cnx.getServer(), userId};
} else {
return userId;
}
} else if (msgType == MessageType.EXCEPTION) {
Object result = part.getObject();
String s = "While performing a remote authenticate";
if (result instanceof AuthenticationFailedException) {
final AuthenticationFailedException afe = (AuthenticationFailedException) result;
if ("REPLY_REFUSED".equals(afe.getMessage())) {
throw new AuthenticationFailedException(s, afe.getCause());
} else {
throw new AuthenticationFailedException(s, afe);
}
} else if (result instanceof AuthenticationRequiredException) {
throw new AuthenticationRequiredException(s, (AuthenticationRequiredException) result);
} else if (result instanceof NotAuthorizedException) {
throw new NotAuthorizedException(s, (NotAuthorizedException) result);
} else {
throw new ServerOperationException(s, (Throwable) result);
}
// Get the exception toString part.
// This was added for c++ thin client and not used in java
} else if (isErrorResponse(msgType)) {
throw new ServerOperationException(part.getString());
} else {
throw new InternalGemFireError("Unexpected message type " + MessageType.getString(msgType));
}
}
@Override
protected boolean isErrorResponse(int msgType) {
return msgType == MessageType.REQUESTDATAERROR;
}
@Override
protected long startAttempt(ConnectionStats stats) {
return stats.startGet();
}
@Override
protected void endSendAttempt(ConnectionStats stats, long start) {
stats.endGetSend(start, hasFailed());
}
@Override
protected void endAttempt(ConnectionStats stats, long start) {
stats.endGet(start, hasTimedOut(), hasFailed());
}
@Override
protected Object processResponse(Message msg) throws Exception {
return null;
}
@Override
protected boolean needsUserId() {
return false;
}
}
}