View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.logic.oidc;
20  
21  import com.nimbusds.oauth2.sdk.ParseException;
22  import com.nimbusds.oauth2.sdk.id.Issuer;
23  import com.nimbusds.openid.connect.sdk.SubjectType;
24  import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
25  import java.io.IOException;
26  import java.net.URI;
27  import java.net.http.HttpClient;
28  import java.net.http.HttpRequest;
29  import java.net.http.HttpResponse;
30  import java.util.ArrayList;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.Optional;
34  import java.util.function.Function;
35  import java.util.stream.Collectors;
36  import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO;
37  import org.apache.syncope.core.persistence.api.entity.OIDCC4UIProvider;
38  import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
39  import org.pac4j.oidc.client.OidcClient;
40  import org.pac4j.oidc.config.OidcConfiguration;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * Basic in-memory cache for available {@link OidcClient} instances.
46   */
47  public class OIDCClientCache {
48  
49      protected static final Logger LOG = LoggerFactory.getLogger(OIDCClientCache.class);
50  
51      protected static final Function<String, String> DISCOVERY_URI =
52              issuer -> issuer + "/.well-known/openid-configuration";
53  
54      public static void importMetadata(final OIDCC4UIProviderTO opTO)
55              throws IOException, InterruptedException, ParseException {
56  
57          String discoveryDocumentURI = DISCOVERY_URI.apply(opTO.getIssuer());
58          HttpResponse<String> response = HttpClient.newBuilder().build().send(
59                  HttpRequest.newBuilder(URI.create(discoveryDocumentURI)).GET().build(),
60                  HttpResponse.BodyHandlers.ofString());
61  
62          OIDCProviderMetadata metadata = OIDCProviderMetadata.parse(response.body());
63  
64          opTO.setIssuer(
65                  Optional.ofNullable(metadata.getIssuer()).map(Issuer::getValue).orElse(null));
66          opTO.setJwksUri(
67                  Optional.ofNullable(metadata.getJWKSetURI()).map(URI::toASCIIString).orElse(null));
68          opTO.setAuthorizationEndpoint(
69                  Optional.ofNullable(metadata.getAuthorizationEndpointURI()).map(URI::toASCIIString).orElse(null));
70          opTO.setTokenEndpoint(
71                  Optional.ofNullable(metadata.getTokenEndpointURI()).map(URI::toASCIIString).orElse(null));
72          opTO.setUserinfoEndpoint(
73                  Optional.ofNullable(metadata.getUserInfoEndpointURI()).map(URI::toASCIIString).orElse(null));
74          opTO.setEndSessionEndpoint(
75                  Optional.ofNullable(metadata.getEndSessionEndpointURI()).map(URI::toASCIIString).orElse(null));
76          Optional.ofNullable(metadata.getScopes()).ifPresent(s -> opTO.getScopes().addAll(s.toStringList()));
77      }
78  
79      protected final List<OidcClient> cache = Collections.synchronizedList(new ArrayList<>());
80  
81      public Optional<OidcClient> get(final String opName) {
82          return cache.stream().filter(c -> opName.equals(c.getName())).findFirst();
83      }
84  
85      public OidcClient add(final OIDCC4UIProvider op, final String callbackUrl) {
86          OIDCProviderMetadata metadata = new OIDCProviderMetadata(
87                  new Issuer(op.getIssuer()),
88                  List.of(SubjectType.PUBLIC),
89                  Optional.ofNullable(op.getJwksUri()).map(URI::create).orElse(null));
90          metadata.setAuthorizationEndpointURI(
91                  Optional.ofNullable(op.getAuthorizationEndpoint()).map(URI::create).orElse(null));
92          metadata.setTokenEndpointURI(
93                  Optional.ofNullable(op.getTokenEndpoint()).map(URI::create).orElse(null));
94          metadata.setUserInfoEndpointURI(
95                  Optional.ofNullable(op.getUserinfoEndpoint()).map(URI::create).orElse(null));
96          metadata.setEndSessionEndpointURI(
97                  Optional.ofNullable(op.getEndSessionEndpoint()).map(URI::create).orElse(null));
98  
99          OidcConfiguration cfg = new OidcConfiguration();
100         cfg.setClientId(op.getClientID());
101         cfg.setSecret(op.getClientSecret());
102         cfg.setProviderMetadata(metadata);
103         cfg.setScope(op.getScopes().stream().collect(Collectors.joining(" ")));
104         cfg.setUseNonce(false);
105         cfg.setLogoutHandler(new NoOpLogoutHandler());
106 
107         OidcClient client = new OidcClient(cfg);
108         client.setName(op.getName());
109         client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
110         client.setCallbackUrl(callbackUrl);
111         client.init();
112 
113         cache.add(client);
114         return client;
115     }
116 
117     public boolean removeAll(final String opName) {
118         return cache.removeIf(c -> opName.equals(c.getName()));
119     }
120 }