Library

Build your own apps based on our implementation

Warning and Disclaimer

We recommend not to use the current Digital ID Library in production. See the roadmap for more information.

Requirements

Since this is currently only a test version, you need Apache Maven to build the library from the source code. For compilation, JDK 8 or newer is required.

Download

Overview

The Digital ID Library is structured in such a way that clients and servers share as much code as possible:

Project Dependencies

Versions

The current versions of these projects are:

Cloning

Clone the maven, utility, database and core projects from GitHub:

git clone https://github.com/synacts/digitalid-maven.git
git clone https://github.com/synacts/digitalid-utility.git
git clone https://github.com/synacts/digitalid-database.git
git clone https://github.com/synacts/digitalid-core.git

Compilation

Build the source code as follows (and in that order):

cd digitalid-maven
mvn clean install
cd ../digitalid-utility
mvn clean install
cd ../digitalid-database
mvn clean install
cd ../digitalid-core
mvn clean install
cd ..

Repository

If you only want to include the utility project for the benefits described in the design section, you can add the following dependency to your POM to load it from the Maven Repository:

<dependency>
    <groupId>net.digitalid.utility</groupId>
    <artifactId>utility-all</artifactId>
    <version>0.7.0</version>
    <type>pom</type>
</dependency>

As soon as the other projects are also more mature, we are going to publish them in the Maven Repository as well.

Usage

Dependencies

If you want to build a client, add the following dependencies to your POM after having compiled the sources:

<dependencies>
    
    <dependency>
        <groupId>net.digitalid.core</groupId>
        <artifactId>core-all</artifactId>
        <version>${project.version}</version>
    </dependency>
    
    <dependency>
        <groupId>net.digitalid.database</groupId>
        <artifactId>database-client</artifactId>
        <version>${project.version}</version>
    </dependency>
    
</dependencies>

Initialization

Various configurations need to be initialized before the library can be used:

import net.digitalid.utility.configuration.Configuration;

Configuration.initializeAllConfigurations();

Client

Before we can create an identity, we first have to create the client with which we will manage the identity:

import net.digitalid.core.client.Client;
import net.digitalid.core.client.ClientBuilder;
import net.digitalid.core.permissions.ReadOnlyAgentPermissions;

final @Nonnull Client client = ClientBuilder.withIdentifier("my.company.client").withDisplayName("Company App").withPreferredPermissions(ReadOnlyAgentPermissions.GENERAL_WRITE).build();

Identifier

The following code checks whether a string is a valid identifier and whether such an identity already exists:

import net.digitalid.core.identification.identifier.InternalNonHostIdentifier;

final @Nonnull String string = "user@digitalid.net";
if (InternalNonHostIdentifier.isValid(string)) {
    final @Nonnull InternalNonHostIdentifier identifier = InternalNonHostIdentifier.with(userString);
    if (!identifier.exists()) {
        // There exists no identity with this identifier and thus a new account can be opened
    }
}

Identity

All actions and queries are performed on roles. You can create a new identity with:

import net.digitalid.core.account.OpenAccount;
import net.digitalid.core.client.role.NativeRole;
import net.digitalid.core.identification.identity.Category;

final @Nonnull NativeRole role = OpenAccount.of(Category.NATURAL_PERSON, identifier, client);

Attribute

Once you have such a role, it is easy to retrieve an attribute of that role:

import net.digitalid.core.attribute.Attribute;
import net.digitalid.core.attribute.AttributeTypes;

final @Nonnull Attribute attribute = Attribute.of(role, AttributeTypes.NAME);

Until we provide better utility methods, setting the value of an attribute is a bit inconvenient:

import net.digitalid.core.pack.Pack;
import net.digitalid.core.pack.PackConverter;
import net.digitalid.core.signature.Signature;
import net.digitalid.core.signature.SignatureBuilder;
import net.digitalid.core.signature.attribute.AttributeValue;
import net.digitalid.core.signature.attribute.UncertifiedAttributeValue;

final @Nonnull Signature<Pack> signature = SignatureBuilder.withObjectConverter(PackConverter.INSTANCE).withObject(Pack.pack(StringConverter.INSTANCE, "My Name", AttributeTypes.NAME)).withSubject(identifier).build();
final @Nonnull AttributeValue value = UncertifiedAttributeValue.with(signature);
attribute.value().set(value);

Setting the (in this example public) visibility of the attribute is then straightforward:

import net.digitalid.core.expression.PassiveExpression;
import net.digitalid.core.expression.PassiveExpressionBuilder;

final @Nonnull PassiveExpression visibility = PassiveExpressionBuilder.withEntity(role).withString("everybody").build();
attribute.visibility().set(visibility);

Caching

The attributes of other identities are cached locally and can be retrieved as follows:

import net.digitalid.core.cache.CacheQueryBuilder;

final @Nonnull InternalNonHostIdentifier friend = new InternalNonHostIdentifier("friend@example.com");
final @Nonnull String name = CacheQueryBuilder.withConverter(StringConverter.INSTANCE).withRequestee(friend.resolve()).withRequester(role).withType(AttributeTypes.NAME).build().execute();

Design

Java Ecosystem

We used the programming language Java for the Digital ID Library due to its wide adoption and the maturity of its ecosystem.

Code Generation

The biggest downside of Java is that you have to write a lot of boilerplate code. We managed to reduce this overhead to a minimum by heavily using Java Annotation Processing to automatically generate builders, subclasses, converters and initializers.

For example,

import net.digitalid.utility.annotations.method.Impure;
import net.digitalid.utility.annotations.method.Pure;
import net.digitalid.utility.generator.annotations.generators.GenerateBuilder;
import net.digitalid.utility.generator.annotations.generators.GenerateSubclass;
import net.digitalid.utility.rootclass.RootClass;

@GenerateBuilder
@GenerateSubclass
public abstract class MutableClass extends RootClass {
    
    @Pure
    public abstract int getValue();
    
    @Impure
    public abstract void setValue(int value);
    
}

generates the subclass (slightly edited here for conciseness)

import java.util.Objects;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.digitalid.utility.annotations.method.Pure;

class MutableClassSubclass extends MutableClass {
    
    private int value;
    
    @Override
    public int getValue() {
        return this.value;
    }
    
    @Override
    public void setValue(int value) {
        this.value = value;
    }
    
    MutableClassSubclass(int value) {
        this.value = value;
    }
    
    @Pure
    @Override
    public boolean equals(@Nullable Object object) {
        if (object == this) {
            return true;
        }
        if (object == null || !(object instanceof MutableClass)) {
            return false;
        }
        final @Nonnull MutableClass that = (MutableClass) object;
        boolean result = true;
        result = result && Objects.equals(this.getValue(), that.getValue());
        return result;
    }
    
    @Pure
    @Override
    public int hashCode() {
        int prime = 92_821;
        int result = 46_411;
        result = prime * result + Objects.hashCode(getValue());
        return result;
    }
    
    @Pure
    @Override
    public @Nonnull String toString() {
        return "MutableClass(value: " + getValue() + ")";
    }
    
}

and the builder (edited here for conciseness)

import javax.annotation.Nonnull;

import net.digitalid.utility.annotations.method.Impure;
import net.digitalid.utility.annotations.method.Pure;

public class MutableClassBuilder {
    
    public static class InnerMutableClassBuilder {
        
        private InnerMutableClassBuilder() {}
        
        private int value = 0;
        
        @Impure
        public @Nonnull InnerMutableClassBuilder withValue(int value) {
            this.value = value;
            return this;
        }
        
        @Pure
        public @Nonnull MutableClass build() {
            return new MutableClassSubclass(value);
        }
        
    }
    
    @Pure
    public static @Nonnull InnerMutableClassBuilder withValue(int value) {
        return new InnerMutableClassBuilder().withValue(value);
    }
    
}

Static Constructors

When we cannot or do not want to generate a builder automatically, we use static methods like of(…) and with(…) instead of public constructors.

This design principle has the following advantages:

  • Possibility to return a cached object instead of a new one, which is important for the observer pattern.
  • Possibility to return null instead of a new object, which is useful to propagate null values.
  • Possibility to perform operations before having to call the constructor of the superclass.
  • Possibility to choose a more descriptive name for the static constructor method.
  • Possibility to construct objects of a subclass instead of the current class.
  • Possibility to make use of these possibilities at some later point in time.
  • And NetBeans correctly checks non-null parameter assignments.

Besides slightly bigger classes, the biggest disadvantage seems to be that static methods are inherited and can thus lead to namespace pollution.

Design by Contract

We follow the design by contract methodology. When you annotate method parameters and return types, the subclass generator generates a subclass that overrides the method and performs the corresponding checks.

For example,

import net.digitalid.utility.annotations.method.Impure;
import net.digitalid.utility.generator.annotations.generators.GenerateSubclass;
import net.digitalid.utility.validation.annotations.math.Positive;

@GenerateSubclass
public abstract class Test {
    
    @Impure
    public void setValue(@Positive int value) { /* … */}
    
}

generates the subclass

import net.digitalid.utility.contracts.Require;
import net.digitalid.utility.validation.annotations.math.Positive;

class TestSubclass extends Test {
    
    @Override
    public void setValue(@Positive int value) {
        Require.that(value > 0).orThrow("The value has to be positive but was $.", value);
        super.setValue(value);
    }
    
}

Type Safety

A built-in form of design by contract is the type safety provided by Java. We tried to use class hierarchies wherever possible instead of relying on annotations to restrict a method parameter or a return type. The downside is that it makes class hierarchies more complex.

Roadmap

Production (v0.7)

  • Auto-incrementing columns
  • Contact management
  • Credential issuance
  • Agent authorization
  • Better test coverage
  • Security review

Self-Hosting (v0.8)

  • Action pusher
  • Audit functionality
  • Domain verification
  • Certificate issuance
  • Client synchronization

Functionality (v0.9)

  • Hierarchical contexts
  • Offline functionality
  • Identity relocation
  • Restricted agents

History

There has been an older, Apache Ant-based version of this library. A lot of things have changed and improved since then. Some artifacts still need to be rewritten, though (see core).

Support

Please file any issues you encounter directly in the corresponding GitHub project (utility, database or core). If you are uncertain to which project an issue belongs, use the last one or contact us directly. When you do so, please attach the current log file. (Since the log file might contain sensitive information, it might be a good idea to first have a look at it yourself.)

License

The code of the Digital ID Library is available under the Apache License 2.0, which is a permissive software license that lets you do anything you want with the code as long as you provide proper attribution and don’t hold us liable.

Structure

The following graphics depict how the artifacts of each project depend on each other. If you only need certain functionality, you can depend on the corresponding artifact directly.

Utility

Utility Dependencies

Database

Database Dependencies

Core

Core Dependencies

Please note that the artifacts with the red border largely still have to be written.