blob: 1e087a90080edd7a07c949dcef0c483e76d3a052 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.kerby.kerberos.kdc.identitybackend;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
import org.apache.directory.api.ldap.model.message.ModifyRequest;
import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.apache.directory.api.util.GeneralizedTime;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.shared.kerberos.KerberosAttribute;
import org.apache.kerby.config.Config;
import org.apache.kerby.kerberos.kerb.KrbException;
import org.apache.kerby.kerberos.kerb.identity.KrbIdentity;
import org.apache.kerby.kerberos.kerb.identity.backend.AbstractIdentityBackend;
import org.apache.kerby.kerberos.kerb.type.KerberosTime;
import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey;
import org.apache.kerby.kerberos.kerb.type.base.EncryptionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* An LDAP based backend implementation.
*/
public class LdapIdentityBackend extends AbstractIdentityBackend {
//The LdapConnection, may be LdapNetworkConnection or LdapCoreSessionConnection
private LdapConnection connection;
//This is used as a flag to represent the connection whether is
// LdapNetworkConnection object or not
private boolean isLdapNetworkConnection;
private static final Logger LOG = LoggerFactory.getLogger(LdapIdentityBackend.class);
public LdapIdentityBackend() {
this.isLdapNetworkConnection = true;
}
/**
* Constructing an instance using specified config that contains anything
* to be used to initialize an LdapConnection and necessary baseDn.
* @param config . The config is used to config the backend.
*/
public LdapIdentityBackend(Config config) {
setConfig(config);
this.isLdapNetworkConnection = true;
}
/**
* Constructing an instance using a LdapConnection and a specified config
* that contains anything to be used to initialize a necessary baseDn.
* @param config The config is used to config the backend
* @param connection The connection to be used to handle the operations,
* may be a LdapNetworkConnection or a LdapCoreSessionConnection.
*/
public LdapIdentityBackend(Config config,
LdapConnection connection) {
setConfig(config);
this.connection = connection;
}
/**
* Start the connection for the initialize()
*/
private void startConnection() throws LdapException {
if (isLdapNetworkConnection == true) {
this.connection = new LdapNetworkConnection(getConfig().getString("host"),
getConfig().getInt("port"));
}
connection.bind(getConfig().getString("admin_dn"),
getConfig().getString("admin_pw"));
}
/**
* {@inheritDoc}
*/
@Override
protected void doInitialize() throws KrbException {
LOG.info("Initializing the Ldap identity backend.");
try {
startConnection();
} catch (LdapException e) {
LOG.error("Failed to start connection with LDAP", e);
throw new KrbException("Failed to start connection with LDAP", e);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void doStop() throws KrbException {
try {
closeConnection();
} catch (IOException e) {
LOG.error("Failed to close connection with LDAP", e);
throw new KrbException("Failed to close connection with LDAP", e);
}
LOG.info("closed connection with LDAP.");
}
/**
* Close the connection for stop()
*/
private void closeConnection() throws IOException {
if (connection.isConnected()) {
connection.close();
}
}
/**
* Convert a KerberosTime type obeject to a generalized time form of String
* @param kerberosTime The kerberostime to convert
*/
private String toGeneralizedTime(KerberosTime kerberosTime) {
GeneralizedTime generalizedTime = new GeneralizedTime(kerberosTime.getValue());
return generalizedTime.toString();
}
/**
* An inner class, used to encapsulate key information
*/
static class KeysInfo {
private String[] etypes;
private byte[][] keys;
private String[] kvnos;
KeysInfo(KrbIdentity identity) throws KrbException {
Map<EncryptionType, EncryptionKey> keymap = identity.getKeys();
this.etypes = new String[keymap.size()];
this.keys = new byte[keymap.size()][];
this.kvnos = new String[keymap.size()];
int i = 0;
for (Map.Entry<EncryptionType, EncryptionKey> entryKey : keymap.entrySet()) {
etypes[i] = entryKey.getKey().getValue() + "";
try {
keys[i] = entryKey.getValue().encode();
} catch (IOException e) {
throw new KrbException("encode key failed", e);
}
kvnos[i] = entryKey.getValue().getKvno() + "";
i++;
}
}
public String[] getEtypes() {
return etypes;
}
public byte[][] getKeys() {
return keys;
}
public String[] getKvnos() {
return kvnos;
}
}
/**
* {@inheritDoc}
*/
@Override
protected KrbIdentity doAddIdentity(KrbIdentity identity) throws KrbException {
String principalName = identity.getPrincipalName();
String[] names = principalName.split("@");
Entry entry = new DefaultEntry();
KeysInfo keysInfo = new KeysInfo(identity);
try {
Dn dn = toDn(principalName);
entry.setDn(dn);
entry.add("objectClass", "top", "person", "inetOrgPerson",
"krb5principal", "krb5kdcentry");
entry.add("cn", names[0]);
entry.add("sn", names[0]);
entry.add(KerberosAttribute.KRB5_KEY_AT, keysInfo.getKeys());
entry.add("krb5EncryptionType", keysInfo.getEtypes());
entry.add(KerberosAttribute.KRB5_PRINCIPAL_NAME_AT, principalName);
entry.add(KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT,
identity.getKeyVersion() + "");
entry.add("krb5KDCFlags", "" + identity.getKdcFlags());
entry.add(KerberosAttribute.KRB5_ACCOUNT_DISABLED_AT, ""
+ identity.isDisabled());
entry.add("createTimestamp",
toGeneralizedTime(identity.getCreatedTime()));
entry.add(KerberosAttribute.KRB5_ACCOUNT_LOCKEDOUT_AT, ""
+ identity.isLocked());
entry.add(KerberosAttribute.KRB5_ACCOUNT_EXPIRATION_TIME_AT,
toGeneralizedTime(identity.getExpireTime()));
connection.add(entry);
} catch (LdapInvalidDnException e) {
LOG.error("Error occurred while adding identity", e);
throw new KrbException("Failed to add identity", e);
} catch (LdapException e) {
LOG.error("Error occurred while adding identity", e);
throw new KrbException("Failed to add identity", e);
}
return getIdentity(principalName);
}
/**
* {@inheritDoc}
*/
@Override
protected KrbIdentity doGetIdentity(String principalName) throws KrbException {
KrbIdentity krbIdentity = new KrbIdentity(principalName);
try {
Dn dn = toDn(principalName);
Entry entry = connection.lookup(dn, "*", "+");
if (entry == null) {
return null;
}
LdapIdentityGetHelper getHelper = new LdapIdentityGetHelper(entry);
krbIdentity.setPrincipal(getHelper.getPrincipalName());
krbIdentity.setKeyVersion(getHelper.getKeyVersion());
krbIdentity.addKeys(getHelper.getKeys());
krbIdentity.setCreatedTime(getHelper.getCreatedTime());
krbIdentity.setExpireTime(getHelper.getExpireTime());
krbIdentity.setDisabled(getHelper.getDisabled());
krbIdentity.setKdcFlags(getHelper.getKdcFlags());
krbIdentity.setLocked(getHelper.getLocked());
} catch (LdapException e) {
throw new KrbException("Failed to retrieve identity", e);
} catch (ParseException e) {
throw new KrbException("Failed to retrieve identity", e);
} catch (IOException e) {
throw new KrbException("Failed to retrieve identity", e);
}
return krbIdentity;
}
/**
* {@inheritDoc}
*/
@Override
protected KrbIdentity doUpdateIdentity(KrbIdentity identity) throws KrbException {
String principalName = identity.getPrincipalName();
KeysInfo keysInfo = new KeysInfo(identity);
try {
Dn dn = toDn(principalName);
ModifyRequest modifyRequest = new ModifyRequestImpl();
modifyRequest.setName(dn);
modifyRequest.replace(KerberosAttribute.KRB5_KEY_VERSION_NUMBER_AT,
"" + identity.getKeyVersion());
modifyRequest.replace(KerberosAttribute.KRB5_KEY_AT, keysInfo.getKeys());
modifyRequest.replace("krb5EncryptionType", keysInfo.getEtypes());
modifyRequest.replace(KerberosAttribute.KRB5_PRINCIPAL_NAME_AT,
identity.getPrincipalName());
modifyRequest.replace(KerberosAttribute.KRB5_ACCOUNT_EXPIRATION_TIME_AT,
toGeneralizedTime(identity.getExpireTime()));
modifyRequest.replace(KerberosAttribute.KRB5_ACCOUNT_DISABLED_AT, ""
+ identity.isDisabled());
modifyRequest.replace("krb5KDCFlags", "" + identity.getKdcFlags());
modifyRequest.replace(KerberosAttribute.KRB5_ACCOUNT_LOCKEDOUT_AT, ""
+ identity.isLocked());
connection.modify(modifyRequest);
} catch (LdapException e) {
LOG.error("Error occurred while updating identity: " + principalName, e);
throw new KrbException("Failed to update identity", e);
}
return getIdentity(principalName);
}
/**
* {@inheritDoc}
*/
@Override
protected void doDeleteIdentity(String principalName) throws KrbException {
try {
Dn dn = toDn(principalName);
connection.delete(dn);
} catch (LdapException e) {
LOG.error("Error occurred while deleting identity: " + principalName);
throw new KrbException("Failed to remove identity", e);
}
}
/**
* Used to convert a dn of String to a Dn object
* @param principalName The principal name to be convert.
* @return
* @throws org.apache.directory.api.ldap.model.exception.LdapInvalidDnException if a remote exception occurs.
*/
private Dn toDn(String principalName) throws LdapInvalidDnException {
String[] names = principalName.split("@");
String uid = names[0];
Dn dn = new Dn(new Rdn("uid", uid), new Dn(getConfig().getString("base_dn")));
return dn;
}
/**
* {@inheritDoc}
*/
@Override
protected Iterable<String> doGetIdentities() {
List<String> identityNames = new ArrayList<>();
EntryCursor cursor;
Entry entry;
try {
cursor = connection.search(getConfig().getString("base_dn"),
"(objectclass=*)", SearchScope.ONELEVEL, KerberosAttribute.KRB5_PRINCIPAL_NAME_AT);
if (cursor == null) {
return null;
}
while (cursor.next()) {
entry = cursor.get();
identityNames.add(entry.get(KerberosAttribute.KRB5_PRINCIPAL_NAME_AT).getString());
}
cursor.close();
Collections.sort(identityNames);
} catch (LdapException e) {
LOG.error("With LdapException when LdapConnection searching. " + e);
} catch (CursorException e) {
LOG.error("With CursorException when EntryCursor getting. " + e);
} catch (IOException e) {
LOG.error("With IOException when closing EntryCursor. " + e);
}
return identityNames;
}
}