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.Acs;
37  import com.linecorp.centraldogma.server.auth.saml.SamlAuthProperties.Idp;
38  import com.linecorp.centraldogma.server.auth.saml.SamlAuthProperties.KeyStore;
39  
40  /**
41   * A factory for creating an OpenSAML based {@link AuthProvider}.
42   */
43  public final class SamlAuthProviderFactory implements AuthProviderFactory {
44      @Override
45      public AuthProvider create(AuthProviderParameters parameters) {
46          final SamlAuthProperties properties = getProperties(parameters.authConfig());
47          try {
48              final KeyStore ks = properties.keyStore();
49              final Idp idp = properties.idp();
50              final SamlServiceProviderBuilder builder = SamlServiceProvider.builder();
51              builder.entityId(properties.entityId())
52                     .hostname(properties.hostname())
53                     .signingKey(properties.signingKey())
54                     .encryptionKey(properties.encryptionKey())
55                     .authorizer(parameters.authorizer())
56                     .ssoHandler(new SamlAuthSsoHandler(
57                             parameters.sessionIdGenerator(),
58                             parameters.loginSessionPropagator(),
59                             Duration.ofMillis(parameters.authConfig().sessionTimeoutMillis()),
60                             parameters.authConfig().loginNameNormalizer(),
61                             properties.idp().subjectLoginNameIdFormat(),
62                             properties.idp().attributeLoginName()))
63                     .credentialResolver(credentialResolver(ks))
64                     .signatureAlgorithm(ks.signatureAlgorithm())
65                     .idp()
66                     .entityId(idp.entityId())
67                     .ssoEndpoint(idp.endpoint())
68                     .signingKey(idp.signingKey())
69                     .encryptionKey(idp.encryptionKey());
70              final Acs acs = properties.acs();
71              if (acs != null && !acs.endpoints().isEmpty()) {
72                  acs.endpoints().forEach(builder::acs);
73              }
74  
75              return new SamlAuthProvider(builder.build());
76          } catch (Exception e) {
77              throw new IllegalStateException("Failed to create " +
78                                              SamlAuthProvider.class.getSimpleName(), e);
79          }
80      }
81  
82      private static SamlAuthProperties getProperties(AuthConfig authConfig) {
83          try {
84              final SamlAuthProperties p = authConfig.properties(SamlAuthProperties.class);
85              checkState(p != null, "authentication properties are not specified");
86              return p;
87          } catch (JsonProcessingException e) {
88              throw new IllegalArgumentException("Failed to get properties from " +
89                                                 AuthConfig.class.getSimpleName(), e);
90          }
91      }
92  
93      private static CredentialResolver credentialResolver(KeyStore keyStore)
94              throws IOException, GeneralSecurityException {
95          final KeyStoreCredentialResolverBuilder builder;
96          final String path = keyStore.path();
97          final File file = new File(path);
98          if (file.isFile()) {
99              builder = new KeyStoreCredentialResolverBuilder(file);
100         } else {
101             builder = new KeyStoreCredentialResolverBuilder(
102                     SamlAuthProviderFactory.class.getClassLoader(), path);
103         }
104 
105         builder.type(keyStore.type())
106                .password(keyStore.password())
107                .keyPasswords(keyStore.keyPasswords());
108         return builder.build();
109     }
110 }