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

FIWARE / credentials-config-service / #41

06 Jun 2025 09:53AM UTC coverage: 84.233% (+0.03%) from 84.199%
#41

Pull #12

wistefan
fix npe
Pull Request #12: Jwt mapping

29 of 46 new or added lines in 4 files covered. (63.04%)

12 existing lines in 2 files now uncovered.

390 of 463 relevant lines covered (84.23%)

0.84 hits per line

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

82.41
/src/main/java/org/fiware/iam/rest/ServiceApiController.java
1
package org.fiware.iam.rest;
2

3
import io.micronaut.core.annotation.NonNull;
4
import io.micronaut.core.annotation.Nullable;
5
import io.micronaut.data.model.Page;
6
import io.micronaut.data.model.Pageable;
7
import io.micronaut.data.model.Sort;
8
import io.micronaut.http.HttpResponse;
9
import io.micronaut.http.annotation.Controller;
10
import io.micronaut.transaction.annotation.Transactional;
11
import lombok.RequiredArgsConstructor;
12
import lombok.extern.slf4j.Slf4j;
13
import org.fiware.iam.ServiceMapper;
14
import org.fiware.iam.ccs.api.ServiceApi;
15
import org.fiware.iam.ccs.model.*;
16
import org.fiware.iam.exception.ConflictException;
17
import org.fiware.iam.repository.Service;
18
import org.fiware.iam.repository.ServiceRepository;
19

20
import java.net.URI;
21
import java.util.HashSet;
22
import java.util.List;
23
import java.util.Optional;
24
import java.util.Set;
25

26
/**
27
 * Implementation of the service api to configure services and there credentials
28
 */
29
@Slf4j
1✔
30
@Controller("${general.basepath:/}")
31
@RequiredArgsConstructor
32
public class ServiceApiController implements ServiceApi {
33

34
        private final ServiceRepository serviceRepository;
35
        private final ServiceMapper serviceMapper;
36

37
        @Override
38
        public HttpResponse<Object> createService(@NonNull ServiceVO serviceVO) {
39
                if (serviceVO.getId() != null && serviceRepository.existsById(serviceVO.getId())) {
1✔
40
                        throw new ConflictException(String.format("The service with id %s already exists.", serviceVO.getId()),
1✔
41
                                        serviceVO.getId());
1✔
42
                }
43
                validateServiceVO(serviceVO);
1✔
44

45
                Service mappedService = serviceMapper.map(serviceVO);
1✔
46

47
                Service savedService = serviceRepository.save(mappedService);
1✔
48

49
                return HttpResponse.created(
1✔
50
                                URI.create(
1✔
51
                                                ServiceApi.PATH_GET_SERVICE.replace(
1✔
52
                                                                "{id}", savedService.getId())));
1✔
53
        }
54

55
        @Override
56
        public HttpResponse<Object> deleteServiceById(@NonNull String id) {
57
                if (!serviceRepository.existsById(id)) {
1✔
58
                        return HttpResponse.notFound();
1✔
59
                }
60
                serviceRepository.deleteById(id);
1✔
61
                return HttpResponse.noContent();
1✔
62
        }
63

64
        @Override
65
        public HttpResponse<ScopeVO> getScopeForService(@NonNull String id, @Nullable String oidcScope) {
66
                Optional<Service> service = serviceRepository.findById(id);
1✔
67
                if (service.isEmpty()) {
1✔
68
                        return HttpResponse.notFound();
1✔
69
                }
70
                String selectedOidcScope =
71
                                oidcScope == null ?
1✔
72
                                                serviceMapper.map(service.get()).getDefaultOidcScope() :
1✔
73
                                                oidcScope;
1✔
74
                ServiceScopesEntryVO serviceScopesEntryVO = serviceMapper.map(service.get())
1✔
75
                                .getOidcScopes()
1✔
76
                                .get(selectedOidcScope);
1✔
77
                ScopeVO scopeVO = new ScopeVO();
1✔
78
                scopeVO.addAll(serviceScopesEntryVO.getCredentials().stream().map(CredentialVO::getType).toList());
1✔
79
                return HttpResponse.ok(scopeVO);
1✔
80
        }
81

82
        @Override
83
        public HttpResponse<ServiceVO> getService(@NonNull String id) {
84
                Optional<ServiceVO> serviceVO = serviceRepository.findById(id)
1✔
85
                                .map(serviceMapper::map);
1✔
86
                return serviceVO
1✔
87
                                .map(HttpResponse::ok)
1✔
88
                                .orElse(HttpResponse.notFound());
1✔
89
        }
90

91
        @Override
92
        public HttpResponse<ServicesVO> getServices(@Nullable Integer nullablePageSize,
93
                                                                                                @Nullable Integer nullablePage) {
94
                var pageSize = Optional.ofNullable(nullablePageSize).orElse(100);
1✔
95
                var page = Optional.ofNullable(nullablePage).orElse(0);
1✔
96
                if (pageSize < 1) {
1✔
97
                        throw new IllegalArgumentException("PageSize has to be at least 1.");
1✔
98
                }
99
                if (page < 0) {
1✔
100
                        throw new IllegalArgumentException("Offsets below 0 are not supported.");
1✔
101
                }
102

103
                Page<Service> requestedPage = serviceRepository.findAll(
1✔
104
                                Pageable.from(page, pageSize, Sort.of(Sort.Order.asc("id"))));
1✔
105
                return HttpResponse.ok(
1✔
106
                                new ServicesVO()
107
                                                .total((int) requestedPage.getTotalSize())
1✔
108
                                                .pageNumber(page)
1✔
109
                                                .pageSize(requestedPage.getContent().size())
1✔
110
                                                .services(requestedPage.getContent().stream().map(serviceMapper::map).toList()));
1✔
111
        }
112

113
        @Transactional
114
        @Override
115
        public HttpResponse<ServiceVO> updateService(@NonNull String id, @NonNull ServiceVO serviceVO) {
116
                if (serviceVO.getId() != null && !id.equals(serviceVO.getId())) {
1✔
UNCOV
117
                        throw new IllegalArgumentException("The id of a service cannot be updated.");
×
118
                }
119
                validateServiceVO(serviceVO);
1✔
120
                if (!serviceRepository.existsById(id)) {
1✔
121
                        return HttpResponse.notFound();
1✔
122
                }
123
                // just in case none is set in the object
124
                serviceVO.setId(id);
1✔
125
                serviceRepository.deleteById(id);
1✔
126
                return HttpResponse.ok(serviceMapper.map(serviceRepository.save(serviceMapper.map(serviceVO))));
1✔
127
        }
128

129
        // validate a service vo, e.g. check forbidden null values
130
        private void validateServiceVO(ServiceVO serviceVO) {
131
                if (serviceVO.getDefaultOidcScope() == null) {
1✔
132
                        throw new IllegalArgumentException("Default OIDC scope cannot be null.");
1✔
133
                }
134
                if (serviceVO.getOidcScopes() == null) {
1✔
135
                        throw new IllegalArgumentException("OIDC scopes cannot be null.");
1✔
136
                }
137

138
                String defaultOidcScope = serviceVO.getDefaultOidcScope();
1✔
139
                ServiceScopesEntryVO serviceScopesEntryVO = serviceVO
1✔
140
                                .getOidcScopes()
1✔
141
                                .get(defaultOidcScope);
1✔
142
                if (serviceScopesEntryVO == null) {
1✔
UNCOV
143
                        throw new IllegalArgumentException("Default OIDC scope must exist in OIDC scopes array.");
×
144
                }
145

146
                Optional<CredentialVO> nullType = serviceScopesEntryVO
1✔
147
                                .getCredentials()
1✔
148
                                .stream()
1✔
149
                                .filter(cvo -> cvo.getType() == null)
1✔
150
                                .findFirst();
1✔
151
                if (nullType.isPresent()) {
1✔
152
                        throw new IllegalArgumentException("Type of a credential cannot be null.");
1✔
153
                }
154

155
                serviceVO
1✔
156
                                .getOidcScopes()
1✔
157
                                .values()
1✔
158
                                .forEach(this::validateKeyMappings);
1✔
159
        }
1✔
160

161
        private void validateKeyMappings(ServiceScopesEntryVO scopeEntry) {
162
                if (scopeEntry.getFlatClaims()) {
1✔
NEW
163
                        List<String> includedKeys = scopeEntry.getCredentials()
×
NEW
164
                                        .stream()
×
NEW
165
                                        .filter(cvo -> cvo.getJwtInclusion().getEnabled())
×
NEW
166
                                        .flatMap(credentialVO ->
×
NEW
167
                                                        credentialVO.getJwtInclusion()
×
NEW
168
                                                                        .getClaimsToInclude()
×
NEW
169
                                                                        .stream()
×
NEW
170
                                                                        .map(claim -> {
×
NEW
171
                                                                                if (claim.getNewKey() != null && !claim.getNewKey().isEmpty()) {
×
NEW
172
                                                                                        return claim.getNewKey();
×
173
                                                                                }
NEW
174
                                                                                return claim.getOriginalKey();
×
175
                                                                        })
NEW
176
                                        ).toList();
×
NEW
177
                        if (includedKeys.size() != new HashSet(includedKeys).size()) {
×
NEW
178
                                throw new IllegalArgumentException("Configuration contains duplicate claim keys.");
×
179
                        }
NEW
180
                } else {
×
181
                        scopeEntry.getCredentials()
1✔
182
                                        .stream()
1✔
183
                                        .filter(cvo -> cvo.getJwtInclusion().getEnabled())
1✔
184
                                        .forEach(cvo -> {
1✔
185
                                                List<String> claimKeys =Optional.ofNullable(cvo.getJwtInclusion()
1✔
186
                                                                .getClaimsToInclude())
1✔
187
                                                                .orElse(List.of())
1✔
188
                                                                .stream()
1✔
189
                                                                .map(claim -> {
1✔
190
                                                                        if (claim.getNewKey() != null && !claim.getNewKey().isEmpty()) {
1✔
191
                                                                                return claim.getNewKey();
1✔
192
                                                                        }
NEW
193
                                                                        return claim.getOriginalKey();
×
194
                                                                })
195
                                                                .toList();
1✔
196
                                                if (claimKeys.size() != new HashSet(claimKeys).size()) {
1✔
NEW
197
                                                        throw new IllegalArgumentException("Configuration contains duplicate claim keys.");
×
198
                                                }
199
                                        });
1✔
200
                }
201
        }
1✔
202
}
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