blob: b04742d698df6335426b513f54eecc6b6788fb90 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.rj.server.srvext.auth;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
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 org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.rj.RjException;
import org.eclipse.statet.rj.server.srvext.ServerAuthMethod;
import org.eclipse.statet.rj.server.util.ServerUtils;
import org.eclipse.statet.rj.server.util.ServerUtils.ArgKeyValue;
/**
* Authentication method 'name-pass'
* to authenticate against a given name password pair.
*/
@NonNullByDefault
public class SimpleNamePassAuthMethod extends ServerAuthMethod {
private Map<String, byte[]> users= nonNullLateInit();
private byte[] digestSash= nonNullLateInit();
private MessageDigest digestService= nonNullLateInit();
private Charset digestCharset= nonNullLateInit();
public SimpleNamePassAuthMethod() {
super("name-pass", true);
}
@Override
public void doInit(final @Nullable String arg) throws RjException {
final ArgKeyValue config= ServerUtils.getArgConfigValue(arg);
try {
this.digestSash= new byte[8];
final SecureRandom random= SecureRandom.getInstance("SHA1PRNG");
random.nextBytes(this.digestSash);
this.digestService= MessageDigest.getInstance("SHA-512");
this.digestCharset= StandardCharsets.UTF_8;
}
catch (final Exception e) {
throw new RjException("", e);
}
final Properties read= new Properties();
if (config.getKey().equals("file")) {
final String fileName= config.getValue();
if (fileName == null || fileName.isEmpty()) {
throw new RjException("Missing password file name.", null);
}
final File file= new File(fileName);
try {
read.load(new FileInputStream(file));
}
catch (final IOException e) {
throw new RjException("Reading password file failed.", null);
}
}
else {
throw new RjException(String.format("Unsupported configuration type '%1$s'.", config.getKey()));
}
this.digestService.update(this.digestSash);
this.users= new HashMap<>();
for (final Map.Entry<?, ?> entry : read.entrySet()) {
final String username= nonNullAssert((String)entry.getKey());
final byte[] password= this.digestService.digest(
this.digestCharset.encode(nonNullAssert((String)entry.getValue())).array() );
this.users.put(username, password);
}
}
@Override
protected ImList<Callback> doCreateLogin() throws RjException {
return ImCollections.newList(
new NameCallback("Loginname"),
new PasswordCallback("Password", false) );
}
@Override
protected String doPerformLogin(final ImList<Callback> callbacks) throws LoginException, RjException {
final String loginName= ((NameCallback)callbacks.get(0)).getName();
if (loginName != null) {
final Object object= this.users.get(loginName);
if (object instanceof byte[]) {
final byte[] loginPassword= getPass((PasswordCallback)callbacks.get(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;
}
}