1   /*
2    * Copyright 2025 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  
17  package com.linecorp.centraldogma.server.internal.admin.auth;
18  
19  import java.util.concurrent.CompletionStage;
20  
21  import javax.annotation.Nullable;
22  
23  import com.linecorp.armeria.common.HttpHeaderNames;
24  import com.linecorp.armeria.common.HttpRequest;
25  import com.linecorp.armeria.common.RequestHeaders;
26  import com.linecorp.armeria.common.auth.BasicToken;
27  import com.linecorp.armeria.common.auth.OAuth2Token;
28  import com.linecorp.armeria.common.util.UnmodifiableFuture;
29  import com.linecorp.armeria.server.ServiceRequestContext;
30  import com.linecorp.armeria.server.auth.AuthTokenExtractors;
31  import com.linecorp.armeria.server.auth.Authorizer;
32  
33  import io.netty.util.AttributeKey;
34  
35  public abstract class AbstractAuthorizer implements Authorizer<HttpRequest> {
36  
37      private static final AttributeKey<String> TOKEN_KEY =
38              AttributeKey.valueOf(AbstractAuthorizer.class, "TOKEN_KEY");
39  
40      private static final String BASIC_USERNAME = "dogma";
41  
42      @Override
43      public CompletionStage<Boolean> authorize(ServiceRequestContext ctx, HttpRequest req) {
44          final String token = extractToken(ctx, req);
45          if (token == null) {
46              return UnmodifiableFuture.completedFuture(false);
47          }
48  
49          return authorize(ctx, req, token);
50      }
51  
52      protected abstract CompletionStage<Boolean> authorize(ServiceRequestContext ctx, HttpRequest req,
53                                                            String accessToken);
54  
55      @Nullable
56      private static String extractToken(ServiceRequestContext ctx, HttpRequest req) {
57          String token = ctx.attr(TOKEN_KEY);
58          if (token != null) {
59              return token;
60          }
61  
62          final RequestHeaders headers = req.headers();
63          final String authorization = headers.get(HttpHeaderNames.AUTHORIZATION);
64          if (authorization == null) {
65              return null;
66          }
67  
68          // Check the scheme of the authorization header to avoid unnecessary warning logs.
69          if (authorization.regionMatches(true, 0, "Bearer", 0, 6)) {
70              final OAuth2Token oAuth2Token = AuthTokenExtractors.oAuth2().apply(headers);
71              if (oAuth2Token != null) {
72                  token = oAuth2Token.accessToken();
73              }
74          } else if (authorization.regionMatches(true, 0, "Basic", 0, 5)) {
75              final BasicToken basicToken = AuthTokenExtractors.basic().apply(headers);
76              if (basicToken != null) {
77                  // Allow only the 'dogma' username for basic authentication.
78                  if (BASIC_USERNAME.equals(basicToken.username())) {
79                      token = basicToken.password();
80                  }
81              }
82          }
83  
84          if (token != null) {
85              ctx.setAttr(TOKEN_KEY, token);
86          }
87          return token;
88      }
89  }