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

FIWARE / trusted-issuers-list / 91

02 Sep 2026 12:52PM UTC coverage: 87.654% (+3.1%) from 84.584%
91

push

github

web-flow
Topic/order scoped credentials (#27)

* Scope credentials to what granted them

A credential entry was identified by its content, so revoking one grant removed a
credential another grant still required. That is a latent defect today - two
product orders can configure the same credential - and it becomes the normal case
once a ServiceSpecification shared by several products carries the
configuration.

Credentials now carry the identifier of whatever granted them, and grants are
addressed by it:

* PUT /issuer/{did}/credential?scope=<id> sets the credentials of one scope to
  exactly the ones provided, leaving every other scope - and every credential
  managed through the issuer endpoints, which carries no scope - untouched.
  Replacing rather than appending makes the call idempotent, which matters
  because the callers react to notifications that are redelivered on failure.
  The issuer is created when unknown, so callers need no create-or-update branch
  and cannot race on one.
* DELETE /issuer/{did}/credential?scope=<id> removes the credentials of one
  scope. The issuer itself is kept even when nothing remains, so an issuer
  somebody else manages is not deleted as a side effect. An issuer without
  credentials is trusted for nothing, which is the same outcome as before.
* A blank scope is rejected: it would create a grant no revocation can address
  precisely, which is the problem the scope exists to solve.

The scope is deliberately NOT part of the API models. Both registry projections
serialize the same CredentialsVO - v4 into the attribute body and hash, v5
additionally into the attribute id, which is a hash of those very bytes - so a
scope in that model would publish grant identifiers in a world-readable registry
and change every v5 attribute id. It travels as a query parameter instead and
lives only on the entity. Two regression tests pin that down by comparing the
projections of an unscoped and a scoped credential.

Because one credential can... (continued)

155 of 195 branches covered (79.49%)

Branch coverage included in aggregate %.

49 of 50 new or added lines in 3 files covered. (98.0%)

40 existing lines in 7 files now uncovered.

626 of 696 relevant lines covered (89.94%)

0.9 hits per line

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

68.42
/src/main/java/org/fiware/iam/TILMapper.java
1
/*
2
 * Copyright 2023 FIWARE Foundation e.V. and/or its affiliates
3
 * and other contributors as indicated by the @author tags.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
package org.fiware.iam;
18

19
import com.fasterxml.jackson.core.JsonProcessingException;
20
import com.fasterxml.jackson.databind.ObjectMapper;
21
import com.fasterxml.jackson.databind.ObjectReader;
22
import com.fasterxml.jackson.databind.ObjectWriter;
23
import java.io.IOException;
24
import java.util.Objects;
25
import org.fiware.iam.repository.Claim;
26
import org.fiware.iam.repository.ClaimValue;
27
import org.fiware.iam.repository.Credential;
28
import org.fiware.iam.repository.TrustedIssuer;
29
import org.fiware.iam.til.model.*;
30
import org.mapstruct.Mapper;
31
import org.mapstruct.Mapping;
32
import org.slf4j.Logger;
33
import org.slf4j.LoggerFactory;
34

35
/** Responsible for mapping entities from the Trusted Issuers List domain to the internal model. */
36
@Mapper(componentModel = "jsr330")
37
public interface TILMapper {
38

39
  Logger LOGGER = LoggerFactory.getLogger(TIRMapper.class);
1✔
40

41
  ObjectReader OBJECT_READER = new ObjectMapper().reader();
1✔
42
  ObjectWriter OBJECT_WRITER = new ObjectMapper().writer();
1✔
43

44
  TrustedIssuer map(TrustedIssuerVO trustedIssuerVO);
45

46
  TrustedIssuerVO map(TrustedIssuer trustedIssuerVO);
47

48
  @Mapping(target = "validFrom", source = "validFor.from")
49
  @Mapping(target = "validTo", source = "validFor.to")
50
  Credential map(CredentialsVO credentialsVO);
51

52
  @Mapping(target = "validFor.from", source = "validFrom")
53
  @Mapping(target = "validFor.to", source = "validTo")
54
  CredentialsVO map(Credential credential);
55

56
  default ClaimVO map(Claim claim) {
57
    return new ClaimVO()
1✔
58
        .name(claim.getName())
1✔
59
        .path(claim.getPath())
1✔
60
        .allowedValues(
1✔
61
            claim.getClaimValues().stream()
1✔
62
                .map(TILMapper::readToObject)
1✔
63
                .filter(Objects::nonNull)
1✔
64
                .toList());
1✔
65
  }
66

67
  default Claim map(ClaimVO claimVO) {
68
    return new Claim()
1✔
69
        .setName(claimVO.getName())
1✔
70
        .setPath(claimVO.getPath())
1✔
71
        .setClaimValues(
1✔
72
            claimVO.getAllowedValues().stream()
1✔
73
                .map(
1✔
74
                    value -> {
75
                      try {
76
                        return OBJECT_WRITER.writeValueAsString(value);
1✔
UNCOV
77
                      } catch (JsonProcessingException e) {
×
UNCOV
78
                        LOGGER.warn(
×
79
                            "Was not able to serialize the claim value {}. Will skip it.",
80
                            value,
81
                            e);
UNCOV
82
                        return null;
×
83
                      }
84
                    })
85
                .filter(Objects::nonNull)
1✔
86
                .map(valueString -> new ClaimValue().setValue(valueString))
1✔
87
                .toList());
1✔
88
  }
89

90
  // in order to also properly read primitives(string,number,boolean) we try to read the value as
91
  // such first and
92
  // ignore potential exceptions and read it as an object just as a last step.
93
  static Object readToObject(ClaimValue claimValue) {
94
    LOGGER.debug("Try to read the claimValue {} to its proper object representation.", claimValue);
1✔
95
    try {
96
      return OBJECT_READER.readValue(claimValue.getValue(), Number.class);
1✔
97
    } catch (IOException ignored) {
1✔
98
      LOGGER.debug("Given value is not a number, try next type.");
1✔
99
    }
100
    try {
101
      return OBJECT_READER.readValue(claimValue.getValue(), String.class);
1✔
UNCOV
102
    } catch (IOException ignored) {
×
UNCOV
103
      LOGGER.debug("Given value is not a string, try next type.");
×
104
    }
105
    try {
UNCOV
106
      return OBJECT_READER.readValue(claimValue.getValue(), Boolean.class);
×
UNCOV
107
    } catch (IOException ignored) {
×
UNCOV
108
      LOGGER.debug("Given value is not a boolean, try next type.");
×
109
    }
110
    try {
UNCOV
111
      return OBJECT_READER.readValue(claimValue.getValue(), Object.class);
×
UNCOV
112
    } catch (IOException e) {
×
UNCOV
113
      LOGGER.warn(
×
114
          "Was not able to read the claimValue {} to an object. Will return null.", claimValue, e);
UNCOV
115
      return null;
×
116
    }
117
  }
118
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc