1   /*
2    * Copyright 2018 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package com.linecorp.centraldogma.server.auth.saml;
17  
18  import static com.google.common.base.Preconditions.checkState;
19  
20  import java.io.File;
21  import java.io.IOException;
22  import java.security.GeneralSecurityException;
23  import java.time.Duration;
24  
25  import org.opensaml.security.credential.CredentialResolver;
26  
27  import com.fasterxml.jackson.core.JsonProcessingException;
28  
29  import com.linecorp.armeria.server.saml.KeyStoreCredentialResolverBuilder;
30  import com.linecorp.armeria.server.saml.SamlServiceProvider;
31  import com.linecorp.armeria.server.saml.SamlServiceProviderBuilder;
32  import com.linecorp.centraldogma.server.auth.AuthConfig;
33  import com.linecorp.centraldogma.server.auth.AuthProvider;
34  import com.linecorp.centraldogma.server.auth.AuthProviderFactory;
35  import com.linecorp.centraldogma.server.auth.AuthProviderParameters;
36  import com.linecorp.centraldogma.server.auth.saml.SamlAuthProperties.Idp;
37  import com.linecorp.centraldogma.server.auth.saml.SamlAuthProperties.KeyStore;
38  
39  /**
40   * A factory for creating an OpenSAML based {@link AuthProvider}.
41   */
42  public final class SamlAuthProviderFactory implements AuthProviderFactory {
43      @Override
44      public AuthProvider create(AuthProviderParameters parameters) {
45          final SamlAuthProperties properties = getProperties(parameters.authConfig());
46          try {
47              final KeyStore ks = properties.keyStore();
48              final Idp idp = properties.idp();
49              final SamlServiceProviderBuilder builder = SamlServiceProvider.builder();
50              builder.entityId(properties.entityId())
51                     .hostname(properties.hostname())
52                     .signingKey(properties.signingKey())
53                     .encryptionKey(properties.encryptionKey())
54                     .authorizer(parameters.authorizer())
55                     .ssoHandler(new SamlAuthSsoHandler(
56                             parameters.sessionIdGenerator(),
57                             parameters.loginSessionPropagator(),
58                             Duration.ofMillis(parameters.authConfig().sessionTimeoutMillis()),
59                             parameters.authConfig().loginNameNormalizer(),
60                             properties.idp().subjectLoginNameIdFormat(),
61                             properties.idp().attributeLoginName()))
62                     .credentialResolver(credentialResolver(ks))
63                     .signatureAlgorithm(ks.signatureAlgorithm())
64                     .idp()
65                     .entityId(idp.entityId())
66                     .ssoEndpoint(idp.endpoint())
67                     .signingKey(idp.signingKey())
68                     .encryptionKey(idp.encryptionKey());
69              return new SamlAuthProvider(builder.build());
70          } catch (Exception e) {
71              throw new IllegalStateException("Failed to create " +
72                                              SamlAuthProvider.class.getSimpleName(), e);
73          }
74      }
75  
76      private static SamlAuthProperties getProperties(AuthConfig authConfig) {
77          try {
78              final SamlAuthProperties p = authConfig.properties(SamlAuthProperties.class);
79              checkState(p != null, "authentication properties are not specified");
80              return p;
81          } catch (JsonProcessingException e) {
82              throw new IllegalArgumentException("Failed to get properties from " +
83                                                 AuthConfig.class.getSimpleName(), e);
84          }
85      }
86  
87      private static CredentialResolver credentialResolver(KeyStore keyStore)
88              throws IOException, GeneralSecurityException {
89          final KeyStoreCredentialResolverBuilder builder;
90          final String path = keyStore.path();
91          final File file = new File(path);
92          if (file.isFile()) {
93              builder = new KeyStoreCredentialResolverBuilder(file);
94          } else {
95              builder = new KeyStoreCredentialResolverBuilder(
96                      SamlAuthProviderFactory.class.getClassLoader(), path);
97          }
98  
99          builder.type(keyStore.type())
100                .password(keyStore.password())
101                .keyPasswords(keyStore.keyPasswords());
102         return builder.build();
103     }
104 }