package osgi.authentication.useradmin.provider;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.useradmin.Role;
import org.osgi.service.useradmin.User;
import org.osgi.service.useradmin.UserAdmin;
import org.slf4j.Logger;
import osgi.authentication.useradmin.provider.Config.Algorithm;
import osgi.enroute.authentication.api.Authenticator;
import osgi.enroute.debug.api.Debug;
import aQute.bnd.annotation.metatype.Configurable;
import aQute.lib.base64.Base64;
import aQute.lib.hex.Hex;
@Component(property = {
Debug.COMMAND_FUNCTION + "=hash", Debug.COMMAND_FUNCTION + "=passwd", Debug.COMMAND_FUNCTION + "=adduser",
Debug.COMMAND_FUNCTION + "=rmrole", Debug.COMMAND_FUNCTION + "=role"
})
public class UserAdminAuthenticator implements Authenticator {
private static final Pattern AUTHORIZATION_P = Pattern.compile("Basic\\s+(?<base64>[A-Za-z0-9+/]{3,}={0,2})");
private static final Pattern IDPW_P = Pattern.compile("(?<id>[^:]+):(?<pw>.*)");
private UserAdmin userAdmin;
private Logger log;
private byte[] salt;
private Algorithm algorithm;
private int iterations;
private String root;
@Activate
void activate(Map<String,Object> args) {
Config config = Configurable.createConfigurable(Config.class, args);
salt = config.salt();
if (salt == null || salt.length == 0) {
salt = new byte[] {
0x2f, 0x68, (byte) 0xcb, 0x75, 0x6c, (byte) 0xf1, 0x74, (byte) 0x84, 0x2a, (byte) 0xef
};
}
algorithm = config.algorithm();
if (algorithm == null) {
algorithm = Algorithm.PBKDF2WithHmacSHA1;
}
iterations = config.iterations();
if (iterations < 100)
iterations = 997;
root = config._root();
if (root != null && root.trim().isEmpty())
root = null;
}
@Override
public String authenticate(Map<String,Object> arguments, String... sources) throws Exception {
for (String source : sources) {
if (Authenticator.BASIC_SOURCE_PASSWORD.equals(source)) {
String id = (String) arguments.get(Authenticator.BASIC_SOURCE_USERID);
String pw = (String) arguments.get(Authenticator.BASIC_SOURCE_PASSWORD);
if (id != null && pw != null)
return verify(id, pw);
log.info("BASIC_SOURCE_PASSWORD specified but no userid/password found in arguments");
}
if (Authenticator.SERVLET_SOURCE.equals(source)) {
String uri = (String) arguments.get(Authenticator.SERVLET_SOURCE_METHOD);
if (uri.startsWith("https:")) {
TreeMap<String,Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
map.putAll(arguments);
String auth = (String) map.get("Authorization");
if (auth != null) {
Matcher m = AUTHORIZATION_P.matcher(auth);
if (m.find()) {
String base64 = m.group("base64");
byte[] bytes = Base64.decodeBase64(base64);
String pwId = new String(bytes, "UTF-8");
m = IDPW_P.matcher(pwId);
if (m.matches()) {
String id = m.group("id");
String pw = m.group("pw");
return verify(id, pw);
}
}
// assume it is not Basic but something else
} else {
log.warn("Servlet authentication requires an Authorization header");
}
} else {
log.warn("Servlet authentication requires https {}", uri);
}
}
}
return null;
}
@Override
public boolean forget(String userid) throws Exception {
return false;
}
private String verify(String id, String pw) throws Exception {
Role role = userAdmin.getRole(id);
if (role == null) {
log.info("Failed login attempt for %s: no such user", id);
return null;
}
if (!(role instanceof User)) {
log.info("Failed login attempt for %s: id is not a user name but %s", id, role);
return null;
}
User user = (User) role;
String hash = hash(pw);
if (user.hasCredential(algorithm.toString(), hash))
return id;
if (root != null && root.equals(hash)) {
log.info("Root login by %s", id);
return id;
}
log.info("Failed login attempt for %s: invalid password", id);
return null;
}
public String hash(String password) throws Exception {
switch (algorithm) {
default :
case PBKDF2WithHmacSHA1 :
byte[] hash = pbkdf2(password.toCharArray(), salt, iterations, 24);
return Hex.toHexString(hash);
}
}
byte[] pbkdf2(char[] password, byte[] salt, int iterations, int bytes) throws Exception {
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8);
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm.toString());
return skf.generateSecret(spec).getEncoded();
}
public Role adduser(String id) {
// TODO validate id
return userAdmin.createRole(id, Role.USER);
}
public List<Role> role(String... filter) throws InvalidSyntaxException {
List<Role> roles = new ArrayList<>();
if (filter.length == 0) {
getRoles(roles, null);
} else {
for (String f : filter) {
if (f.startsWith("(") && f.endsWith(")")) {
getRoles(roles, f);
} else {
Role r = userAdmin.getRole(f);
if (f != null)
roles.add(r);
}
}
}
return roles;
}
private void getRoles(List<Role> roles, String filter) throws InvalidSyntaxException {
Role[] rs = userAdmin.getRoles(filter);
if (rs != null) {
for (Role role : rs) {
roles.add(role);
}
}
}
public int rmrole(String... id) {
int n = 0;
for (String i : id) {
userAdmin.removeRole(i);
n++;
}
return n;
}
@SuppressWarnings("unchecked")
public void passwd(String id, String pw) throws Exception {
Role role = userAdmin.getRole(id);
if (role == null) {
role = userAdmin.createRole(id, Role.USER);
} else if (!(role instanceof User)) {
System.err.println("Not a user role, but " + role);
}
User user = (User) role;
user.getCredentials().put(algorithm.toString(), hash(pw));
}
@Reference
void setUA(UserAdmin userAdmin) {
this.userAdmin = userAdmin;
}
@Reference
void setLog(Logger logger) {
this.log = logger;
}
}