AuthController.java

package no.nav.data.common.security;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import no.nav.data.common.exceptions.TechnicalException;
import no.nav.data.common.security.dto.OAuthState;
import no.nav.data.common.security.dto.UserInfo;
import no.nav.data.common.security.dto.UserInfoResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;

import static no.nav.data.common.security.SecurityConstants.COOKIE_NAME;
import static no.nav.data.common.utils.Constants.SESSION_LENGTH;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.*;
import static org.springframework.security.web.util.UrlUtils.buildFullRequestUrl;

@Slf4j
@RestController
@RequestMapping
@RequiredArgsConstructor
@Tag(name = "Auth")
public class AuthController {

    public static final String OAUTH_2_CALLBACK_URL = "/oauth2/callback";
    private static final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    private final SecurityProperties securityProperties;
    private final TokenProvider tokenProvider;
    private final Encryptor encryptor;

    @Operation(summary = "Login using sso")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "302", description = "Redirect to sso")
    })
    @GetMapping("/login")
    public void login(HttpServletRequest request, HttpServletResponse response,
                      @RequestParam(value = REDIRECT_URI, required = false) String redirectUri,
                      @RequestParam(value = ERROR_URI, required = false) String errorUri
    ) throws IOException {
        log.debug("Request to login");
        Assert.isTrue(securityProperties.isValidRedirectUri(redirectUri), "Illegal redirect_uri " + redirectUri);
        Assert.isTrue(securityProperties.isValidRedirectUri(errorUri), "Illegal error_uri " + errorUri);
        var usedRedirect = redirectUri != null ? redirectUri : securityProperties.findBaseUrl();
        String redirectUrl = tokenProvider.createAuthRequestRedirectUrl(usedRedirect, errorUri, callbackRedirectUri(request));
        redirectStrategy.sendRedirect(request, response, redirectUrl);
    }

    @Operation(summary = "oidc callback")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "302", description = "token accepted")
    })
    @CrossOrigin
    @PostMapping(OAUTH_2_CALLBACK_URL)
    public void oidc(HttpServletRequest request, HttpServletResponse response,
                     @RequestParam(value = CODE, required = false) String code,
                     @RequestParam(value = ERROR, required = false) String error,
                     @RequestParam(value = ERROR_DESCRIPTION, required = false) String errorDesc,
                     @RequestParam(STATE) String stateJson
    ) throws IOException {
        log.debug("Request to auth");
        OAuthState state;
        try {
            state = OAuthState.fromJson(stateJson, encryptor);
        } catch (Exception e) {
            throw new TechnicalException("invalid state", e);
        }
        if (StringUtils.hasText(code)) {
            var session = tokenProvider.createSession(state.getSessionId(), code, callbackRedirectUri(request));
            response.addCookie(createCookie(session, (int) SESSION_LENGTH.toSeconds(), request));
            redirectStrategy.sendRedirect(request, response, state.getRedirectUri());
        } else {
            String errorRedirect = state.errorRedirect(error, errorDesc);
            log.warn("error logging in {}", errorRedirect);
            redirectStrategy.sendRedirect(request, response, errorRedirect);
        }
    }

    @Operation(summary = "Logout")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "302", description = "Logged out"),
            @ApiResponse(responseCode = "200", description = "Logged out")
    })
    @GetMapping("/logout")
    public void logout(HttpServletRequest request, HttpServletResponse response,
                       @RequestParam(value = REDIRECT_URI, required = false) String redirectUri
    ) throws IOException {
        log.debug("Request to logout");
        Assert.isTrue(securityProperties.isValidRedirectUri(redirectUri), "Illegal redirect_uri " + redirectUri);
        tokenProvider.destroySession();
        response.addCookie(createCookie(null, 0, request));
        if (redirectUri != null) {
            redirectStrategy.sendRedirect(request, response, new OAuthState(redirectUri).getRedirectUri());
        }
    }

    @Operation(summary = "User info")
    @ApiResponse(description = "userinfo returned")
    @GetMapping("/userinfo")
    public ResponseEntity<UserInfoResponse> userinfo() {
        log.debug("Request to userinfo");
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated() || "anonymousUser".equals(authentication.getPrincipal())) {
            return ResponseEntity.ok(UserInfoResponse.noUser(securityProperties.isEnabled()));
        }
        return ResponseEntity.ok(((UserInfo) authentication.getDetails()).convertToResponse());
    }

    public static Cookie createCookie(String value, int maxAge, HttpServletRequest request) {
        Cookie cookie = new Cookie(COOKIE_NAME, value);
        cookie.setMaxAge(maxAge);
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setSecure(!"localhost".equals(request.getServerName()));
        return cookie;
    }

    private String callbackRedirectUri(HttpServletRequest request) {
        String redirectUri = UriComponentsBuilder.fromUriString(buildFullRequestUrl(request))
                .replacePath(OAUTH_2_CALLBACK_URL)
                .replaceQuery(null).build().toUriString();
        Assert.isTrue(securityProperties.isValidRedirectUri(redirectUri), "Invalid redirect uri " + redirectUri);
        return redirectUri;
    }
}