content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.dolphinscheduler.api.controller;
  
  import static org.apache.dolphinscheduler.api.enums.Status.IP_IS_EMPTY;
  import static org.apache.dolphinscheduler.api.enums.Status.NOT_SUPPORT_SSO;
  import static org.apache.dolphinscheduler.api.enums.Status.SIGN_OUT_ERROR;
  import static org.apache.dolphinscheduler.api.enums.Status.USER_LOGIN_FAILURE;
  
  import org.apache.dolphinscheduler.api.configuration.OAuth2Configuration;
  import org.apache.dolphinscheduler.api.enums.Status;
  import org.apache.dolphinscheduler.api.exceptions.ApiException;
  import org.apache.dolphinscheduler.api.security.Authenticator;
  import org.apache.dolphinscheduler.api.security.impl.AbstractSsoAuthenticator;
  import org.apache.dolphinscheduler.api.service.SessionService;
  import org.apache.dolphinscheduler.api.service.UsersService;
  import org.apache.dolphinscheduler.api.utils.Result;
  import org.apache.dolphinscheduler.common.constants.Constants;
  import org.apache.dolphinscheduler.common.enums.UserType;
  import org.apache.dolphinscheduler.common.utils.JSONUtils;
  import org.apache.dolphinscheduler.common.utils.OkHttpUtils;
  import org.apache.dolphinscheduler.dao.entity.Session;
  import org.apache.dolphinscheduler.dao.entity.User;
  
  import org.apache.commons.lang3.StringUtils;
  import org.apache.http.HttpStatus;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.UUID;
  import java.util.stream.Collectors;
  
  import javax.servlet.http.Cookie;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.http.HttpServletResponse;
  import javax.servlet.http.HttpSession;
  
  import lombok.SneakyThrows;
  import lombok.extern.slf4j.Slf4j;
  
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.web.bind.annotation.DeleteMapping;
  import org.springframework.web.bind.annotation.GetMapping;
  import org.springframework.web.bind.annotation.PostMapping;
  import org.springframework.web.bind.annotation.RequestAttribute;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RequestParam;
  import org.springframework.web.bind.annotation.RestController;
  
  import io.swagger.v3.oas.annotations.Operation;
  import io.swagger.v3.oas.annotations.Parameter;
  import io.swagger.v3.oas.annotations.Parameters;
  import io.swagger.v3.oas.annotations.media.Schema;
  import io.swagger.v3.oas.annotations.tags.Tag;
  
  /**
   * login controller
   */
  @Tag(name = "LOGIN_TAG")
  @RestController
  @RequestMapping("")
  @Slf4j
  public class LoginController extends BaseController {
  
      @Autowired
      private SessionService sessionService;
  
      @Autowired
      private Authenticator authenticator;
  
      @Autowired(required = false)
      private OAuth2Configuration oAuth2Configuration;
  
      @Autowired
      private UsersService usersService;
  
      /**
       * login
       *
       * @param userName user name
       * @param userPassword user password
       * @param request request
       * @param response response
       * @return login result
       */
      @Operation(summary = "login", description = "LOGIN_NOTES")
      @Parameters({
              @Parameter(name = "userName", description = "USER_NAME", required = true, schema = @Schema(implementation = String.class)),
              @Parameter(name = "userPassword", description = "USER_PASSWORD", required = true, schema = @Schema(implementation = String.class))
      })
      @PostMapping(value = "/login")
      @ApiException(USER_LOGIN_FAILURE)
      public Result login(@RequestParam(value = "userName") String userName,
                          @RequestParam(value = "userPassword") String userPassword,
                          HttpServletRequest request,
                          HttpServletResponse response) {
          // user name check
          if (StringUtils.isEmpty(userName)) {
              return error(Status.USER_NAME_NULL.getCode(),
                      Status.USER_NAME_NULL.getMsg());
          }
  
          // user ip check
          String ip = getClientIpAddress(request);
          if (StringUtils.isEmpty(ip)) {
              return error(IP_IS_EMPTY.getCode(), IP_IS_EMPTY.getMsg());
          }
  
          // verify username and password
          Result<Map<String, String>> result = authenticator.authenticate(userName, userPassword, ip);
          if (result.getCode() != Status.SUCCESS.getCode()) {
              return result;
          }
  
          response.setStatus(HttpStatus.SC_OK);
          Map<String, String> cookieMap = result.getData();
          for (Map.Entry<String, String> cookieEntry : cookieMap.entrySet()) {
              Cookie cookie = new Cookie(cookieEntry.getKey(), cookieEntry.getValue());
              cookie.setHttpOnly(true);
              response.addCookie(cookie);
          }
  
          return result;
      }
  
      /**
       * sso login
       *
       * @return sso server url
       */
      @Operation(summary = "sso login", description = "SSO_LOGIN_NOTES")
      @GetMapping(value = "/login/sso")
      @ApiException(NOT_SUPPORT_SSO)
      public Result ssoLogin(HttpServletRequest request) {
          if (authenticator instanceof AbstractSsoAuthenticator) {
              String randomState = UUID.randomUUID().toString();
              HttpSession session = request.getSession();
              if (session.getAttribute(Constants.SSO_LOGIN_USER_STATE) == null) {
                  session.setAttribute(Constants.SSO_LOGIN_USER_STATE, randomState);
              }
              return Result.success(((AbstractSsoAuthenticator) authenticator).getSignInUrl(randomState));
          }
          return Result.success();
      }
  
      @Operation(summary = "signOut", description = "SIGN_OUT_NOTES")
      @PostMapping(value = "/signOut")
      @ApiException(SIGN_OUT_ERROR)
      public Result signOut(@Parameter(hidden = true) @RequestAttribute(value = Constants.SESSION_USER) User loginUser,
                            HttpServletRequest request) {
          String ip = getClientIpAddress(request);
          sessionService.expireSession(loginUser.getId());
          // clear session
          request.removeAttribute(Constants.SESSION_USER);
          return success();
      }
  
      @DeleteMapping("cookies")
      public void clearCookieSessionId(HttpServletRequest request, HttpServletResponse response) {
          Cookie[] cookies = request.getCookies();
          for (Cookie cookie : cookies) {
              cookie.setMaxAge(0);
              cookie.setValue(null);
              response.addCookie(cookie);
          }
          response.setStatus(HttpStatus.SC_OK);
      }
  
      @Operation(summary = "getOauth2Provider", description = "GET_OAUTH2_PROVIDER")
      @GetMapping("oauth2-provider")
      public Result<List<OAuth2Configuration.OAuth2ClientProperties>> oauth2Provider() {
          if (oAuth2Configuration == null) {
              return Result.success(new ArrayList<>());
          }
  
          Collection<OAuth2Configuration.OAuth2ClientProperties> values = oAuth2Configuration.getProvider().values();
          List<OAuth2Configuration.OAuth2ClientProperties> providers = values.stream().map(e -> {
              OAuth2Configuration.OAuth2ClientProperties oAuth2ClientProperties =
                      new OAuth2Configuration.OAuth2ClientProperties();
              oAuth2ClientProperties.setAuthorizationUri(e.getAuthorizationUri());
              oAuth2ClientProperties.setRedirectUri(e.getRedirectUri());
              oAuth2ClientProperties.setClientId(e.getClientId());
              oAuth2ClientProperties.setProvider(e.getProvider());
              oAuth2ClientProperties.setIconUri(e.getIconUri());
              return oAuth2ClientProperties;
          }).collect(Collectors.toList());
          return Result.success(providers);
      }
  
      @SneakyThrows
      @Operation(summary = "redirectToOauth2", description = "REDIRECT_TO_OAUTH2_LOGIN")
      @GetMapping("redirect/login/oauth2")
      public void loginByAuth2(@RequestParam String code, @RequestParam String provider,
                               HttpServletRequest request, HttpServletResponse response) {
          OAuth2Configuration.OAuth2ClientProperties oAuth2ClientProperties =
                  oAuth2Configuration.getProvider().get(provider);
          try {
              Map<String, String> tokenRequestHeader = new HashMap<>();
              tokenRequestHeader.put("Accept", "application/json");
              Map<String, Object> requestBody = new HashMap<>(16);
              requestBody.put("client_secret", oAuth2ClientProperties.getClientSecret());
              HashMap<String, Object> requestParamsMap = new HashMap<>();
              requestParamsMap.put("client_id", oAuth2ClientProperties.getClientId());
              requestParamsMap.put("code", code);
              requestParamsMap.put("grant_type", "authorization_code");
              requestParamsMap.put("redirect_uri",
                      String.format("%s?provider=%s", oAuth2ClientProperties.getRedirectUri(), provider));
              String tokenJsonStr = OkHttpUtils.post(oAuth2ClientProperties.getTokenUri(), tokenRequestHeader,
                      requestParamsMap, requestBody);
              String accessToken = JSONUtils.getNodeString(tokenJsonStr, "access_token");
              Map<String, String> userInfoRequestHeaders = new HashMap<>();
              userInfoRequestHeaders.put("Accept", "application/json");
              Map<String, Object> userInfoQueryMap = new HashMap<>();
              userInfoQueryMap.put("access_token", accessToken);
              userInfoRequestHeaders.put("Authorization", "Bearer " + accessToken);
              String userInfoJsonStr =
                      OkHttpUtils.get(oAuth2ClientProperties.getUserInfoUri(), userInfoRequestHeaders, userInfoQueryMap);
              String username = JSONUtils.getNodeString(userInfoJsonStr, "login");
              User user = usersService.getUserByUserName(username);
              if (user == null) {
                  user = usersService.createUser(UserType.GENERAL_USER, username, null);
              }
              Session session = sessionService.createSessionIfAbsent(user);
              response.setStatus(HttpStatus.SC_MOVED_TEMPORARILY);
              response.sendRedirect(String.format("%s?sessionId=%s&authType=%s", oAuth2ClientProperties.getCallbackUrl(),
                      session.getId(), "oauth2"));
          } catch (Exception ex) {
              log.error(ex.getMessage(), ex);
              response.setStatus(HttpStatus.SC_MOVED_TEMPORARILY);
              response.sendRedirect(String.format("%s?authType=%s&error=%s", oAuth2ClientProperties.getCallbackUrl(),
                      "oauth2", "oauth2 auth error"));
          }
      }
  }
  