/*=============================================================================#
# Copyright (c) 2009-2016 Stephan Wahlbrink (WalWare.de) and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of either (per the licensee's choosing)
# - the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html, or
# - the GNU Lesser General Public License v2.1 or newer
# which accompanies this distribution, and is available at
# http://www.gnu.org/licenses/lgpl.html
#
# Contributors:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.rj.server.srvstdext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import de.walware.rj.RjException;
import de.walware.rj.server.srvext.ServerAuthMethod;
import de.walware.rj.server.srvext.ServerUtil;
/**
* Authentication method 'name-pass'
* to authenticate against a given name password pair.
*/
public class SimpleNamePassAuthMethod extends ServerAuthMethod {
private Properties users;
private byte[] digestSash;
private MessageDigest digestService;
private Charset digestCharset;
public SimpleNamePassAuthMethod() {
super("name-pass", true);
}
@Override
public void doInit(final String arg) throws RjException {
final String configType;
final String configValue;
{ final String[] args = ServerUtil.getArgConfigValue(arg);
configType = args[0];
configValue = args[1];
}
try {
this.digestSash = new byte[8];
final SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.nextBytes(this.digestSash);
this.digestService = MessageDigest.getInstance("SHA-512");
this.digestCharset = Charset.forName("UTF-8");
}
catch (final Exception e) {
throw new RjException("", e);
}
if (configType.equals("file")) {
if (configValue == null || configValue.length() == 0) {
throw new RjException("Missing password file name.", null);
}
final File file = new File(configValue);
this.users = new Properties();
try {
this.users.load(new FileInputStream(file));
}
catch (final IOException e) {
throw new RjException("Reading password file failed.", null);
}
}
else {
throw new RjException("Unsupported configuration type '"+configType+"'.", null);
}
this.digestService.update(this.digestSash);
final Set<Entry<Object,Object>> entrySet = this.users.entrySet();
for (final Entry<Object, Object> entry : entrySet) {
final byte[] password = this.digestService.digest(this.digestCharset.encode(
(String) entry.getValue()).array());
entry.setValue(password);
}
System.gc();
}
@Override
protected Callback[] doCreateLogin() throws RjException {
return new Callback[] {
new NameCallback("Loginname"),
new PasswordCallback("Password", false),
};
}
@Override
protected String doPerformLogin(final Callback[] callbacks) throws LoginException, RjException {
final String loginName = ((NameCallback) callbacks[0]).getName();
final Object object = this.users.get(loginName);
if (object instanceof byte[]) {
final byte[] loginPassword = getPass((PasswordCallback) callbacks[1]);
if (Arrays.equals((byte[]) object, loginPassword)) {
return loginName;
}
}
throw new FailedLoginException("Invalid loginname or password");
}
private byte[] getPass(final PasswordCallback callback) {
final char[] loginPassword = callback.getPassword();
final byte[] loginBytes;
if (loginPassword == null) {
return new byte[0];
}
this.digestService.update(this.digestSash);
loginBytes = this.digestService.digest(this.digestCharset.encode(
CharBuffer.wrap(loginPassword)).array());
callback.clearPassword();
Arrays.fill(loginPassword, (char) 0);
return loginBytes;
}
}