/*
* Copyright 2011 Federal Chancellery Austria and
* Graz University of Technology
*
* Licensed 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 at.gv.util.data;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import at.gv.util.BpkUtil;
import at.gv.util.DOMUtils;
import at.gv.util.MiscUtil;
import at.gv.util.ToStringUtil;
import at.gv.util.ex.EgovUtilException;
import at.gv.util.ex.InternalErrorException;
import at.gv.util.xsd.mandate.Mandate;
import at.gv.util.xsd.persondata.IdentificationType;
import at.gv.util.xsd.persondata.PersonDataType;
import at.gv.util.xsd.persondata.PhysicalPersonType;
import at.gv.util.xsd.saml.assertion.AssertionType;
import at.gv.util.xsd.saml.assertion.AttributeStatementType;
import at.gv.util.xsd.saml.assertion.AttributeType;
import at.gv.util.xsd.saml.assertion.NameIdentifierType;
import at.gv.util.xsd.saml.assertion.ObjectFactory;
import at.gv.util.xsd.saml.assertion.StatementAbstractType;
import at.gv.util.xsd.saml.assertion.SubjectConfirmationType;
import at.gv.util.xsd.saml.assertion.SubjectType;
/**
* @author Arne Tauber
* @author Thomas Knall
*/
public final class ElectronicIdentity implements Serializable, Empty, RoleContainer {
private static final long serialVersionUID = 1L;
private Logger log = LoggerFactory.getLogger(this.getClass().getName());
private static final String FIRSTNAME = "firstname";
private static final String LASTNAME = "lastname";
private static final String TITLE = "title";
private static final String DATEOFBIRTH = "dateofbirth";
private static final String EMAIL = "email";
private static final String BASEID = "baseid";
private static final String BPK = "bpk";
private static final String WBPK = "wbpk";
private static final String VZBPK = "vzbpk";
private static final String ZBPK = "zbpk";
private static final String BKU_URL = "bkuurl";
private static final String ROLES = "roles";
private static final String ROLE = "role";
private static final String NAME_QUALIFIER = "namequalifier";
private static final String NAME_IDENTIFIER = "nameidentifier";
private static final String MANDATE_ENABLED = "mandateenabled";
private static final String BPK_NAME_QUALIFIER = "urn:publicid:gv.at:cdid+bpk";
private static final String WBPK_NAME_QUALIFIER_PREFIX = "urn:publicid:gv.at:wbpk";
private static final String BASE_NAME_QUALIFIER = "urn:publicid:gv.at:baseid";
private String firstName;
private String title;
private String lastName;
private Date dateOfBirth;
private String bpk;
private String wbpk;
private String baseId;
private String vzbpk;
private String zbpk;
private String email;
private String bkuURL;
private String nameQualifier;
private String nameIdentifier;
private Set roles;
private Object userdefinedData;
private AssertionType samlAssertion;
private Mandate mandate;
public String getTitle() {
return this.title;
}
public Object getUserdefinedData() {
return this.userdefinedData;
}
public void setUserdefinedData(Object userdefinedData) {
this.userdefinedData = userdefinedData;
}
public void setTitle(String title) {
this.title = title;
}
public String getBkuURL() {
return this.bkuURL;
}
public void setBkuURL(String bkuURL) {
this.bkuURL = bkuURL;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public String getWbpk() {
return this.wbpk;
}
public void setWbpk(String wbpk) {
this.wbpk = wbpk;
}
public String getNameQualifier() {
return this.nameQualifier;
}
public void setNameQualifier(String nameQualifier) {
this.nameQualifier = nameQualifier;
}
public String getNameIdentifier() {
return this.nameIdentifier;
}
public void setNameIdentifier(String nameIdentifier) {
this.nameIdentifier = nameIdentifier;
}
public String getZbpk() {
return this.zbpk;
}
public void setZbpk(String zbpk) {
this.zbpk = zbpk;
}
public String getVzbpk() {
return this.vzbpk;
}
public void setVzbpk(String vzbpk) {
this.vzbpk = vzbpk;
}
public String getBaseId() {
return this.baseId;
}
public void setBaseId(String baseId) {
this.baseId = baseId;
}
public String getBpk() {
return this.bpk;
}
public void setBpk(String bpk) {
this.bpk = bpk;
}
public AssertionType getSamlAssertion() {
return this.samlAssertion;
}
public Date getDateOfBirth() {
return this.dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Set getRoles() {
return this.roles;
}
public void setRoles(Set roles) {
MiscUtil.assertNotNull(roles, "Roles");
this.roles = roles;
}
public ElectronicIdentity addRole(String role) {
MiscUtil.assertNotEmpty(role, "Role");
this.roles.add(role);
return this;
}
public ElectronicIdentity() {
}
public ElectronicIdentity(String firstName, String lastName, Date dateOfBirth) {
this();
this.setFirstName(firstName);
this.setLastName(lastName);
this.setDateOfBirth(dateOfBirth);
}
public ElectronicIdentity(String firstName, String lastName, String email) {
this();
this.setFirstName(firstName);
this.setLastName(lastName);
this.setEmail(email);
}
private void updateAll() {
if (this.getNameQualifier() != null
&& this.getNameQualifier().startsWith(WBPK_NAME_QUALIFIER_PREFIX)
&& MiscUtil.isEmpty(this.getNameIdentifier())) {
log.debug("NameQualifier starts with \"" + WBPK_NAME_QUALIFIER_PREFIX
+ "\" and BaseId is present. Calculating NameIdentifier as wbpk.");
this.nameIdentifier = BpkUtil.calcWBPK(this.getBaseId(),
this.getNameQualifier());
}
if (this.getNameQualifier() != null
&& this.getNameQualifier().startsWith(WBPK_NAME_QUALIFIER_PREFIX)
&& MiscUtil.isEmpty(this.getWbpk())
&& MiscUtil.isNotEmpty(this.getNameIdentifier())) {
log.debug("NameQualifier starts with \"" + WBPK_NAME_QUALIFIER_PREFIX
+ "\". We have a wbpk.");
this.wbpk = this.getNameIdentifier();
}
if (BPK_NAME_QUALIFIER.equals(this.getNameQualifier())
&& MiscUtil.isEmpty(this.getBpk())
&& MiscUtil.isNotEmpty(this.getNameIdentifier())) {
log.debug("NameQualifier equals to \"" + BPK_NAME_QUALIFIER
+ "\". We have a bpk.");
this.bpk = this.getNameIdentifier();
}
if (MiscUtil.isNotEmpty(this.getBaseId())) {
log.debug("BaseId present -> calculating zbpk");
this.zbpk = BpkUtil.calcZBPK(this.getBaseId());
}
}
void setSamlAssertion(AssertionType samlAssertion) {
this.samlAssertion = samlAssertion;
}
public ElectronicIdentity(Document doc) throws EgovUtilException {
this(doc, false);
}
public ElectronicIdentity(Document doc, boolean isMOAIDAssertion) throws EgovUtilException {
this();
try {
MiscUtil.assertNotNull(doc, "Document");
JAXBContext ctx = JAXBContext.newInstance(AssertionType.class
.getPackage().getName());
JAXBElement assertionElement = (JAXBElement) ctx
.createUnmarshaller().unmarshal(doc.getDocumentElement());
if (isMOAIDAssertion) {
initializeCitizenCardWithMOAIDAssertion(assertionElement.getValue());
} else {
initializeCitizenCard(assertionElement.getValue());
}
} catch (JAXBException e) {
throw new EgovUtilException(e);
}
}
public ElectronicIdentity(AssertionType assertion) throws EgovUtilException {
this(assertion, false);
}
public ElectronicIdentity(AssertionType assertion, boolean isMOAIDAssertion) throws EgovUtilException {
this();
// debug moa-id response
log.trace("Debug response: {}", System.getProperty("debug.moaid.log.path") != null);
if (System.getProperty("debug.moaid.log.path") != null) {
try {
ObjectFactory of = new ObjectFactory();
JAXBContext ctx = JAXBContext.newInstance(AssertionType.class.getPackage().getName());
String file = System.getProperty("debug.moaid.log.path") + "/" + MiscUtil.formatDate(new Date(), "yyyyMMdd-HHmmss") +".xml";
log.trace("Writing MOA-ID response to: " + file);
FileOutputStream fos = new FileOutputStream(file);
ctx.createMarshaller().marshal(of.createAssertion(assertion), fos);
fos.flush();
fos.close();
} catch(Exception e) {
log.debug(e.getMessage(), e);
}
}
if (isMOAIDAssertion) {
initializeCitizenCardWithMOAIDAssertion(assertion);
} else {
initializeCitizenCard(assertion);
}
}
private void initializeCitizenCard(AssertionType assertion)
throws EgovUtilException {
MiscUtil.assertNotNull(assertion, "SAMLAssertion");
try {
for (StatementAbstractType sat : assertion
.getStatementOrSubjectStatementOrAuthenticationStatement()) {
if (sat instanceof AttributeStatementType) {
AttributeStatementType attrStmt = (AttributeStatementType) sat;
SubjectType subject = attrStmt.getSubject();
for (JAXBElement> subChild : subject.getContent()) {
if (subChild.getValue() instanceof SubjectConfirmationType) {
SubjectConfirmationType sct = (SubjectConfirmationType) subChild
.getValue();
Element scdNode = (Element) sct.getSubjectConfirmationData();
Element personNode = (Element) DOMUtils
.getChildElements(scdNode).get(0);
JAXBContext ctx = JAXBContext.newInstance(PhysicalPersonType.class
.getPackage().getName());
JAXBElement pptElement = (JAXBElement) ctx
.createUnmarshaller().unmarshal(personNode);
PhysicalPersonType ppt = pptElement.getValue();
this.baseId = ppt.getIdentification().get(0).getValue().getValue();
this.firstName = ppt.getName().getGivenName().get(0);
this.lastName = ppt.getName().getFamilyName().get(0).getValue();
this.dateOfBirth = MiscUtil.parseXMLDate(ppt.getDateOfBirth(), TimeZone.getTimeZone("UTC"));
}
}
}
}
} catch(JAXBException e) {
throw new EgovUtilException(e);
}
}
private void initializeCitizenCardWithMOAIDAssertion(AssertionType assertion)
throws EgovUtilException {
MiscUtil.assertNotNull(assertion, "SAMLAssertion");
try {
AttributeStatementType attrStmt = (AttributeStatementType) assertion
.getStatementOrSubjectStatementOrAuthenticationStatement().get(0);
// parse subject
SubjectType subject = attrStmt.getSubject();
for (JAXBElement> subChild : subject.getContent()) {
if (subChild.getValue() instanceof SubjectConfirmationType) {
SubjectConfirmationType sct = (SubjectConfirmationType) subChild
.getValue();
Element scdNode = (Element) sct.getSubjectConfirmationData();
if (scdNode.hasChildNodes()) {
Element assertionNode = (Element) DOMUtils.getChildElements(scdNode)
.get(0);
JAXBContext ctx = JAXBContext.newInstance(AssertionType.class.getPackage().getName());
JAXBElement assertionElement = (JAXBElement) ctx
.createUnmarshaller().unmarshal(assertionNode);
AssertionType subjectAssertion = assertionElement.getValue();
for (StatementAbstractType sat : subjectAssertion
.getStatementOrSubjectStatementOrAuthenticationStatement()) {
if (sat instanceof AttributeStatementType) {
AttributeStatementType ast = (AttributeStatementType) sat;
for (AttributeType attr : ast.getAttribute()) {
if ("bPK".equals(attr.getAttributeName())) {
Element attrValueNode = (Element) attr.getAttributeValue()
.get(0);
Element idNode = (Element) DOMUtils.getChildElements(
attrValueNode).get(0);
ctx = JAXBContext.newInstance(IdentificationType.class
.getPackage().getName());
JAXBElement idElement = (JAXBElement) ctx
.createUnmarshaller().unmarshal(idNode);
IdentificationType idt = (IdentificationType) idElement
.getValue();
//this.setBpk(idt.getValue().getValue());
}
}
}
}
}
} else if (subChild.getValue() instanceof NameIdentifierType) {
NameIdentifierType nit = (NameIdentifierType) subChild.getValue();
this.setNameQualifier(nit.getNameQualifier());
this.setNameIdentifier(nit.getValue());
}
}
for (AttributeType attr : attrStmt.getAttribute()) {
if ("PersonData".equals(attr.getAttributeName())) {
Element attrValueNode = (Element) attr.getAttributeValue().get(0);
Element personNode = (Element) DOMUtils.getChildElements(
attrValueNode).get(0);
JAXBContext ctx = JAXBContext.newInstance(PhysicalPersonType.class
.getPackage().getName());
JAXBElement pptElement = (JAXBElement) ctx
.createUnmarshaller().unmarshal(personNode);
PhysicalPersonType ppt = pptElement.getValue();
String baseId = ppt.getIdentification().get(0).getValue().getValue();
this.setBaseId(baseId);
this.setZbpk(BpkUtil.calcZBPK(baseId));
this.setVzbpk(BpkUtil.calcVZBPK(baseId));
this.setDateOfBirth(MiscUtil.parseXMLDate(ppt.getDateOfBirth(), TimeZone.getTimeZone("UTC")));
this.setFirstName(ppt.getName().getGivenName().get(0));
this.setLastName(ppt.getName().getFamilyName().get(0).getValue());
} else if ("bkuURL".equals(attr.getAttributeName())) {
Node attrValueNode = (Node) attr.getAttributeValue().get(0);
this.setBkuURL(attrValueNode.getFirstChild().getNodeValue());
} else if ("Mandate".equals(attr.getAttributeName())) {
Element attrValueNode = (Element) attr.getAttributeValue().get(0);
List mandateElementList = DOMUtils.getChildElements(attrValueNode);
if (mandateElementList != null && mandateElementList.size() > 0) {
// parse mandate
JAXBContext ctx = JAXBContext.newInstance(Mandate.class.getPackage().getName());
this.mandate = (Mandate) ctx.createUnmarshaller().unmarshal((Element) mandateElementList.get(0));
}
}
}
} catch (JAXBException e) {
throw new EgovUtilException(e);
}
}
/**
* Creates a wrapper for buergerkarte person data.
* Important note: properties-files are supposed to contain ISO 8859-1
* character encoding
*
* @param properties
* Properties containing buergerkarte person data as key/value pairs
*/
public ElectronicIdentity(Properties properties) {
this();
this.evaluateProperties(properties);
}
/**
* Fills wrapper with buergerkarte person data from a Properties file.
* Important note: properties-files are supposed to contain ISO 8859-1
* character encoding
*
* @param properties
* Properties containing buergerkarte person data as key/value pairs
* @throws CannotResetException
*/
private void evaluateProperties(Properties properties) {
if (properties != null) {
this.setFirstName(properties.getProperty(FIRSTNAME));
this.setLastName(properties.getProperty(LASTNAME));
if (properties.getProperty(DATEOFBIRTH) != null) {
try {
this.setDateOfBirth(DateUtils.parseDate(
properties.getProperty(DATEOFBIRTH), new String[] { "yyyy-MM-dd",
"dd.MM.yyyy", }));
} catch (ParseException e) {
log.error(e.getMessage(), e);
}
}
this.setTitle(properties.getProperty(TITLE));
this.setBpk(properties.getProperty(BPK));
this.setWbpk(properties.getProperty(WBPK));
this.setBaseId(properties.getProperty(BASEID));
this.setNameIdentifier(properties.getProperty(NAME_IDENTIFIER));
this.setNameQualifier(properties.getProperty(NAME_QUALIFIER));
if (MiscUtil.isEmpty(this.getBaseId())
&& BASE_NAME_QUALIFIER.equals(this.getNameQualifier())) {
this.setBaseId(this.getNameIdentifier());
}
this.setEmail(properties.getProperty(EMAIL));
this.setVzbpk(properties.getProperty(VZBPK));
this.setZbpk(properties.getProperty(ZBPK));
this.setBkuURL(properties.getProperty(BKU_URL));
String roles = properties.getProperty(ROLES);
if (MiscUtil.isNotEmpty(roles)) {
StringTokenizer tokenizer = new StringTokenizer(roles, ",");
while (tokenizer.hasMoreTokens()) {
String role = StringUtils.trim(tokenizer.nextToken());
if (MiscUtil.isNotEmpty(role)) {
this.roles.add(role);
}
}
}
String role = StringUtils.trim(properties.getProperty(ROLE));
if (MiscUtil.isNotEmpty(role)) {
this.roles.add(role);
}
this.updateAll();
}
}
public void calcBpk(String domain) {
if (MiscUtil.isEmpty(this.getBaseId())) {
throw new InternalErrorException(
"Unable to calculate bpk. BaseId has to be set.");
}
if (MiscUtil.isEmpty(domain)) {
throw new IllegalArgumentException(
"Unable to calculate bpk. Target/sector/domain must not be empty.");
}
this.bpk = BpkUtil.calcBPK(this.getBaseId(), domain);
}
public void calcWbpk() {
MiscUtil.assertNotEmpty(this.getBaseId(), "BaseId");
MiscUtil.assertNotEmpty(this.getNameQualifier(), "NameQualifier");
this.wbpk = BpkUtil.calcWBPK(this.getBaseId(), this.getNameQualifier());
this.nameIdentifier = this.wbpk;
}
protected void calcVzbpk(byte[] rsaPublicKey, String domain) {
MiscUtil.assertNotEmpty(domain, "Domain");
if (MiscUtil.isEmpty(this.getBaseId())) {
throw new InternalErrorException(
"Unable to calculate bpk. BaseId has to be set.");
}
MiscUtil.assertNotEmpty(rsaPublicKey, "RSAPublicKey");
PublicKey publicKey;
try {
KeyFactory rsaKeyFac = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(rsaPublicKey);
publicKey = (RSAPublicKey) rsaKeyFac.generatePublic(keySpec);
} catch (InvalidKeySpecException e) {
throw new InternalErrorException(e);
} catch (NoSuchAlgorithmException e) {
throw new InternalErrorException(e);
}
this.vzbpk = BpkUtil.calcVZBPK(this.getBaseId(), publicKey);
}
public void calcVzbpk(byte[] rasPublicKey) {
this.calcVzbpk(rasPublicKey, BpkUtil.SECTOR_DELIVERY);
}
public void calcVzbpk() {
this.calcVzbpk(BpkUtil.PUBLIC_KEY_ZUSEKOPF_SN01_BASE64.getBytes(),
BpkUtil.SECTOR_DELIVERY);
}
public void calcZbpk() {
if (MiscUtil.isEmpty(this.getBaseId())) {
throw new InternalErrorException(
"Unable to calculate bpk. BaseId has to be set.");
}
this.setZbpk(BpkUtil.calcZBPK(this.getBaseId()));
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("firstName", this.firstName)
.append("lastName", this.lastName)
.append(
"dateOfBirth",
this.dateOfBirth != null ? DateFormatUtils.format(this.dateOfBirth,
"yyyy-MM-dd") : this.dateOfBirth)
.append("title", this.title)
.append("email", this.email)
//.append("baseId", this.baseId)
.append("nameQualifier", this.nameQualifier)
.append("nameIdentifier", this.nameIdentifier)
.append("bpk", this.bpk)
.append("wbpk", this.wbpk)
.append("zbpk", this.zbpk)
.append("vzbpk", this.vzbpk)
.append("bkuURL", this.bkuURL)
.append("userdefinedData", this.userdefinedData)
.append(
"roles",
this.roles != null ? ToStringUtil.toString(this.roles, ", ", "\"")
: null)
.append("samlAssertion",
this.samlAssertion != null ? "" : "").toString();
}
public boolean isEmpty() {
boolean stringsEmpty = MiscUtil.areAllEmpty(this.wbpk, this.nameQualifier,
this.nameIdentifier, this.baseId, this.bpk, this.firstName,
this.lastName, this.vzbpk, this.zbpk, this.email, this.bkuURL);
boolean udEmpty = true;
if (this.userdefinedData != null) {
if (this.userdefinedData instanceof Empty) {
udEmpty = ((Empty) this.userdefinedData).isEmpty();
} else {
udEmpty = false;
}
}
return stringsEmpty && udEmpty && this.dateOfBirth == null
&& MiscUtil.isEmpty(this.roles) && (this.samlAssertion != null);
}
public boolean hasRole(String role) {
return this.roles.contains(role);
}
public void setMandate(Mandate mandate) {
this.mandate = mandate;
}
public Mandate getMandate() {
return mandate;
}
}