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.wa.starter.webauthn;
20  
21  import com.fasterxml.jackson.core.type.TypeReference;
22  import com.yubico.data.CredentialRegistration;
23  import java.util.Collection;
24  import java.util.List;
25  import java.util.stream.Collectors;
26  import java.util.stream.Stream;
27  import org.apache.syncope.common.lib.SyncopeClientException;
28  import org.apache.syncope.common.lib.types.ClientExceptionType;
29  import org.apache.syncope.common.lib.wa.WebAuthnAccount;
30  import org.apache.syncope.common.lib.wa.WebAuthnDeviceCredential;
31  import org.apache.syncope.common.rest.api.service.wa.WebAuthnRegistrationService;
32  import org.apache.syncope.wa.bootstrap.WARestClient;
33  import org.apereo.cas.configuration.CasConfigurationProperties;
34  import org.apereo.cas.util.crypto.CipherExecutor;
35  import org.apereo.cas.webauthn.WebAuthnUtils;
36  import org.apereo.cas.webauthn.storage.BaseWebAuthnCredentialRepository;
37  import org.jooq.lambda.Unchecked;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  public class WAWebAuthnCredentialRepository extends BaseWebAuthnCredentialRepository {
42  
43      protected static final Logger LOG = LoggerFactory.getLogger(WAWebAuthnCredentialRepository.class);
44  
45      protected final WARestClient waRestClient;
46  
47      public WAWebAuthnCredentialRepository(final CasConfigurationProperties properties,
48              final WARestClient waRestClient) {
49          super(properties, CipherExecutor.noOpOfStringToString());
50          this.waRestClient = waRestClient;
51      }
52  
53      protected WebAuthnRegistrationService service() {
54          return waRestClient.getService(WebAuthnRegistrationService.class);
55      }
56  
57      @Override
58      public boolean removeRegistrationByUsername(
59              final String username,
60              final CredentialRegistration credentialRegistration) {
61  
62          String id = credentialRegistration.getCredential().getCredentialId().getHex();
63          service().delete(username, id);
64          return true;
65      }
66  
67      @Override
68      public boolean removeAllRegistrations(final String username) {
69          service().delete(username);
70          return true;
71      }
72  
73      @Override
74      public Stream<? extends CredentialRegistration> stream() {
75          return service().list().
76                  stream().
77                  map(WebAuthnAccount::getCredentials).
78                  flatMap(Collection::stream).
79                  map(Unchecked.function(record -> {
80                      String json = getCipherExecutor().decode(record.getJson());
81                      return WebAuthnUtils.getObjectMapper().readValue(json, new TypeReference<>() {
82                      });
83                  }));
84      }
85  
86      @Override
87      protected void update(final String username, final Collection<CredentialRegistration> records) {
88          try {
89              List<WebAuthnDeviceCredential> credentials = records.stream().
90                      map(Unchecked.function(record -> {
91                          String json = getCipherExecutor().encode(WebAuthnUtils.getObjectMapper().
92                                  writeValueAsString(record));
93                          return new WebAuthnDeviceCredential.Builder().
94                                  json(json).
95                                  identifier(record.getCredential().getCredentialId().getHex()).
96                                  build();
97                      })).
98                      collect(Collectors.toList());
99  
100             WebAuthnAccount account = service().read(username);
101             if (account != null) {
102                 account.getCredentials().addAll(credentials);
103                 service().update(username, account);
104             } else {
105                 account = new WebAuthnAccount.Builder().credentials(credentials).build();
106                 service().create(username, account);
107             }
108         } catch (final Exception e) {
109             LOG.error(e.getMessage(), e);
110         }
111     }
112 
113     @Override
114     public Collection<CredentialRegistration> getRegistrationsByUsername(final String username) {
115         try {
116             return service().read(username).getCredentials().stream().
117                     map(Unchecked.function(record -> {
118                         String json = getCipherExecutor().decode(record.getJson());
119                         return WebAuthnUtils.getObjectMapper()
120                                 .readValue(json, new TypeReference<CredentialRegistration>() {
121                                 });
122                     })).
123                     collect(Collectors.toList());
124         } catch (SyncopeClientException e) {
125             if (e.getType() == ClientExceptionType.NotFound) {
126                 LOG.info("Could not locate account for {}", username);
127             } else {
128                 LOG.error(e.getMessage(), e);
129             }
130         } catch (Exception e) {
131             LOG.error(e.getMessage(), e);
132         }
133         return List.of();
134     }
135 }