| /*=============================================================================# |
| # 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; |
| } |
| |
| } |