/**
* i-net software provides programming examples for illustration only, without warranty
* either expressed or implied, including, but not limited to, the implied warranties
* of merchantability and/or fitness for a particular purpose. This programming example
* assumes that you are familiar with the programming language being demonstrated and
* the tools used to create and debug procedures. i-net software support professionals
* can help explain the functionality of a particular procedure, but they will not modify
* these examples to provide added functionality or construct procedures to meet your
* specific needs.
*
* Copyright © 1999-2025 i-net software GmbH, Berlin, Germany.
**/
package com.inet.authentication.passwordfile;
/*
either expressed or implied, including, but not limited to, the implied warranties
of merchantability and/or fitness for a particular purpose. This programming example
assumes that you are familiar with the programming language being demonstrated and
the tools used to create and debug procedures. i-net software support professionals
can help explain the functionality of a particular procedure, but they will not modify
these examples to provide added functionality or construct procedures to meet your
specific needs.
© i-net software 1998-2017
*/
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* It is responsible for reading data from password file defined in configuration for Password File Authentication.
* Content of the password file must be UTF-8-encoded. Each text line of the file must represent data of single
* user:
*
* userName:realm:ha1
*
* where ha1 is MD5 hash value of A1 which is defined by RFC7616 as
* A1 = unq(username) ":" unq(realm) ":" passwd
*
* For example:
* bob:i-net Clear Reports:253e2ac5a9c0c3d0fc0a5092ba886cec
*/
public class PasswordFile {
private String passwordFile;
private Map map = new HashMap<>();
private String previousFilePath = null;
private long previousLastModified = -1;
/**
* Set the current password file.
*
* @param passwordFile current file;
*/
void setPasswordFile( String passwordFile ) {
this.passwordFile = passwordFile;
}
/**
* Returns hash value of A1 for given user and realm or null.
*
* @param userName name of the user from the request.
* @param realm realm from the request.
* @return hash value of A1 for given user and realm or null.
* @throws IOException if an I/O error occurs reading from the file or a malformed or unmappable byte sequence is
* read.
*/
public synchronized String getHashValueOfA1( String userName, String realm ) throws IOException {
try {
loadData();
} catch( IOException ex ) {
map.clear();
previousFilePath = null;
previousLastModified = -1;
throw ex;
}
return map.get( userName + ":" + realm );
}
/**
* Loads data from password file, if needed. It does nothing if path that denotes password file and its time of last
* modification have not been changed since last call.
*
* @throws IOException if an I/O error occurs reading from the file or a malformed or unmappable byte sequence is
* read.
*/
private void loadData() throws IOException {
String filePath = passwordFile;
Path passwordFile = Paths.get( filePath );
long lastModified = passwordFile.toFile().lastModified();
if( previousFilePath != null && previousFilePath.equals( filePath ) && previousLastModified == lastModified ) {
return;
}
map.clear();
List allLines = Files.readAllLines( passwordFile, StandardCharsets.UTF_8 );
for( String line : allLines ) {
int index = line.lastIndexOf( ":" );
if( index <= 0 || index == line.length() - 1 ) {
continue;
}
String key = line.substring( 0, index );
String value = line.substring( index + 1 );
map.put( key, value );
}
previousFilePath = filePath;
previousLastModified = lastModified;
}
}