• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wistefan / keycloak-vc-issuer / #424

14 Nov 2023 02:06PM UTC coverage: 41.163% (-7.7%) from 48.895%
#424

push

wistefan
use protocol mappers

354 of 860 relevant lines covered (41.16%)

0.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.04
src/main/java/org/fiware/keycloak/SIOP2ClientRegistrationProvider.java
1
package org.fiware.keycloak;
2

3
import jakarta.ws.rs.Consumes;
4
import jakarta.ws.rs.DELETE;
5
import jakarta.ws.rs.POST;
6
import jakarta.ws.rs.PUT;
7
import jakarta.ws.rs.Path;
8
import jakarta.ws.rs.PathParam;
9
import jakarta.ws.rs.Produces;
10
import jakarta.ws.rs.core.MediaType;
11
import jakarta.ws.rs.core.Response;
12
import org.jboss.logging.Logger;
13
import org.keycloak.models.KeycloakSession;
14
import org.keycloak.representations.idm.ClientRepresentation;
15
import org.keycloak.services.ErrorResponseException;
16
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
17

18
import java.net.URI;
19
import java.util.HashMap;
20
import java.util.Map;
21
import java.util.Optional;
22
import java.util.UUID;
23
import java.util.stream.Collectors;
24

25
/**
26
 * Provides the client-registration functionality for siop2-clients.
27
 */
28
public class SIOP2ClientRegistrationProvider extends AbstractClientRegistrationProvider {
29

30
        private static final Logger LOGGER = Logger.getLogger(SIOP2ClientRegistrationProvider.class);
1✔
31

32
        public static final String EXPIRY_IN_MIN = "expiryInMin";
33
        public static final String VC_CLAIMS_PREFIX = "vc_";
34
        public static final String VC_TYPES_PREFIX = "vctypes_";
35

36
        public SIOP2ClientRegistrationProvider(KeycloakSession session) {
37
                super(session);
×
38
        }
×
39

40
        // CUD implementations for the SIOP-2 client
41

42
        @POST
43
        @Consumes(MediaType.APPLICATION_JSON)
44
        @Produces(MediaType.APPLICATION_JSON)
45
        public Response createSIOP2Client(SIOP2Client client) {
46
                LOGGER.infof("Create siop client %s", client);
×
47
                ClientRepresentation clientRepresentation = toClientRepresentation(client);
×
48
                validate(clientRepresentation);
×
49

50
                ClientRepresentation cr = create(
×
51
                                new SIOP2ClientRegistrationContext(session, clientRepresentation, this));
52
                URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(cr.getClientId()).build();
×
53
                return Response.created(uri).entity(cr).build();
×
54
        }
55

56
        @PUT
57
        @Path("{clientId}")
58
        @Consumes(MediaType.APPLICATION_JSON)
59
        @Produces(MediaType.APPLICATION_JSON)
60
        public Response updateSIOP2Client(@PathParam("clientId") String clientDid, SIOP2Client client) {
61
                client.setClientDid(clientDid);
×
62
                ClientRepresentation clientRepresentation = toClientRepresentation(client);
×
63
                validate(clientRepresentation);
×
64
                clientRepresentation = update(clientDid,
×
65
                                new SIOP2ClientRegistrationContext(session, clientRepresentation, this));
66
                return Response.ok(clientRepresentation).build();
×
67
        }
68

69
        @DELETE
70
        @Path("{clientId}")
71
        public Response deleteSIOP2Client(@PathParam("clientId") String clientDid) {
72
                delete(clientDid);
×
73
                return Response.noContent().build();
×
74
        }
75

76
        /**
77
         * Validates the clientrepresentation to fulfill the requirement of a SIOP-2 client
78
         *
79
         * @param client
80
         */
81
        public static void validate(ClientRepresentation client) {
82
                String did = client.getClientId();
1✔
83
                if (did == null) {
1✔
84
                        throw new ErrorResponseException("no_did", "A client did needs to be configured for SIOP-2 clients",
1✔
85
                                        Response.Status.BAD_REQUEST);
86
                }
87
                if (!did.startsWith("did:")) {
1✔
88
                        // TODO: future implementations should check the actual validity of a did, instead of just the format
89
                        throw new ErrorResponseException("invalid_did", "The client did is not a valid did.",
1✔
90
                                        Response.Status.BAD_REQUEST);
91
                }
92
        }
1✔
93

94
        /**
95
         * Translate an incoming {@link SIOP2Client} into a keycloak native {@link ClientRepresentation}.
96
         *
97
         * @param siop2Client pojo, containing the SIOP-2 client parameters
98
         * @return a clientrepresentation, fitting keycloaks internal model
99
         */
100
        protected static ClientRepresentation toClientRepresentation(SIOP2Client siop2Client) {
101
                ClientRepresentation clientRepresentation = new ClientRepresentation();
1✔
102
                // protocol needs to be SIOP-2
103
                clientRepresentation.setProtocol(SIOP2LoginProtocolFactory.PROTOCOL_ID);
1✔
104
                // id and clientId cannot be equal since did's might be to long, already validated to be non-null
105
                clientRepresentation.setId(UUID.randomUUID().toString());
1✔
106
                clientRepresentation.setClientId(siop2Client.getClientDid());
1✔
107
                // only add non-null parameters
108
                Optional.ofNullable(siop2Client.getDescription()).ifPresent(clientRepresentation::setDescription);
1✔
109
                Optional.ofNullable(siop2Client.getName()).ifPresent(clientRepresentation::setName);
1✔
110

111
                // add potential additional claims
112
                Map<String, String> clientAttributes = new HashMap<>(
1✔
113
                                prefixClaims(VC_CLAIMS_PREFIX, siop2Client.getAdditionalClaims()));
1✔
114

115
                // only set expiry if present
116
                Optional.ofNullable(siop2Client.getExpiryInMin())
1✔
117
                                .ifPresent(expiry -> clientAttributes.put(EXPIRY_IN_MIN, String.format("%s", expiry)));
1✔
118
                // only set supported VCs if present
119
                if (siop2Client.getSupportedVCTypes() != null) {
1✔
120
                        siop2Client.getSupportedVCTypes()
1✔
121
                                        .forEach(supportedCredential -> {
1✔
122
                                                String typeKey = String.format("%s%s", VC_TYPES_PREFIX, supportedCredential.getType());
1✔
123
                                                if (clientAttributes.containsKey(typeKey)) {
1✔
124
                                                        clientAttributes.put(typeKey, String.format("%s,%s",
×
125
                                                                        clientAttributes.get(typeKey),
×
126
                                                                        supportedCredential.getFormat().toString()));
×
127
                                                } else {
128
                                                        clientAttributes.put(typeKey,
1✔
129
                                                                        supportedCredential.getFormat().toString());
1✔
130
                                                }
131
                                        });
1✔
132
                }
133
                if (!clientAttributes.isEmpty()) {
1✔
134
                        clientRepresentation.setAttributes(clientAttributes);
1✔
135
                }
136

137
                LOGGER.debugf("Generated client representation {}.", clientRepresentation);
1✔
138
                return clientRepresentation;
1✔
139
        }
140

141
        /**
142
         * Prefix the map of claims, to differentiate them from potential internal once. Only the prefixed claims will be
143
         * used for creating VCs.
144
         */
145
        private static Map<String, String> prefixClaims(String prefix, Map<String, String> claimsToPrefix) {
146
                if (claimsToPrefix == null) {
1✔
147
                        return Map.of();
1✔
148
                }
149
                return claimsToPrefix.entrySet()
1✔
150
                                .stream()
1✔
151
                                .collect(
1✔
152
                                                Collectors
153
                                                                .toMap(e -> String.format("%s%s", prefix, e.getKey()),
1✔
154
                                                                                Map.Entry::getValue));
155
        }
156
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc