package io.jenkins.blueocean.service.embedded;

import hudson.model.User;
import io.jenkins.blueocean.auth.jwt.JwtToken;
import io.jenkins.blueocean.commons.ServiceException;
import org.acegisecurity.providers.AbstractAuthenticationToken;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.MalformedClaimException;
import org.jose4j.jwt.NumericDate;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.jwt.consumer.JwtContext;
import org.jose4j.jwx.JsonWebStructure;
import org.jose4j.lang.JoseException;
import org.kohsuke.stapler.StaplerRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collections;

import static org.jose4j.jws.AlgorithmIdentifiers.RSA_USING_SHA256;

/**
 * @author Kohsuke Kawaguchi
 */
public final class JwtAuthenticationToken extends AbstractAuthenticationToken{

    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationToken.class);


    private final String name;

    public JwtAuthenticationToken(StaplerRequest request) {
        String authHeader = request.getHeader("Authorization");
        if(authHeader == null || !authHeader.startsWith("Bearer")){
            throw new ServiceException.UnauthorizedException("JWT token not found");
        }
        String token = authHeader.substring("Bearer ".length());
        try {
            JsonWebStructure jws = JsonWebStructure.fromCompactSerialization(token);
            String alg = jws.getAlgorithmHeaderValue();
            if(alg == null || !alg.equals(RSA_USING_SHA256)){
                logger.error(String.format("Invalid JWT token: unsupported algorithm in header, found %s, expected %s", alg, RSA_USING_SHA256));
                throw new ServiceException.UnauthorizedException("Invalid JWT token");
            }

            String kid = jws.getKeyIdHeaderValue();

            if(kid == null){
                logger.error("Invalid JWT token: missing kid");
                throw new ServiceException.UnauthorizedException("Invalid JWT token");
            }

            JwtToken.JwtRsaDigitalSignatureKey key = new JwtToken.JwtRsaDigitalSignatureKey(kid);
            try {
                if(!key.exists()){
                    throw new ServiceException.NotFoundException(String.format("kid %s not found", kid));
                }
            } catch (IOException e) {
                logger.error(String.format("Error reading RSA key for id %s: %s",kid,e.getMessage()),e);
                throw new ServiceException.UnexpectedErrorException("Unexpected error: "+e.getMessage(), e);
            }

            JwtConsumer jwtConsumer = new JwtConsumerBuilder()
                .setRequireExpirationTime() // the JWT must have an expiration time
                .setRequireJwtId()
                .setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
                .setRequireSubject() // the JWT must have a subject claim
                .setVerificationKey(key.getPublicKey()) // verify the sign with the public key
                .build(); // create the JwtConsumer instance

            try {
                JwtContext context = jwtConsumer.process(token);
                JwtClaims claims = context.getJwtClaims();

                //check if token expired
                NumericDate expirationTime = claims.getExpirationTime();
                if (expirationTime.isBefore(NumericDate.now())){
                    throw new ServiceException.UnauthorizedException("Invalid JWT token: expired");
                }
                String subject = claims.getSubject();
                if(!subject.equals("anonymous")) { //if anonymous, we don't look in user db
                    User user = User.get(subject, false, Collections.emptyMap());
                    if (user == null) {
                        throw new ServiceException.UnauthorizedException("Invalid JWT token: subject " + subject + " not found");
                    }
                }
                this.name = subject;
            } catch (InvalidJwtException e) {
                logger.error("Invalid JWT token: "+e.getMessage(), e);
                throw new ServiceException.UnauthorizedException("Invalid JWT token");
            } catch (MalformedClaimException e) {
                logger.error(String.format("Error reading sub header for token %s",jws.getPayload()),e);
                throw new ServiceException.UnauthorizedException("Invalid JWT token: malformed claim");
            }
        } catch (JoseException e) {
            logger.error("Error parsing JWT token: "+e.getMessage(), e);
            throw new ServiceException.UnexpectedErrorException("Unexpected error");
        }
    }

    @Override
    public Object getCredentials() {
        return "";
    }

    @Override
    public Object getPrincipal() {
        return name;
    }

    @Override
    public String getName() {
        return name;
    }
}

@@ -160,32 +160,152 @@
 rviceException;%0A
+import jenkins.model.Jenkins;%0Aimport org.acegisecurity.GrantedAuthority;%0Aimport org.acegisecurity.GrantedAuthorityImpl;%0A
 import org.acegi
@@ -348,24 +348,74 @@
 ationToken;%0A
+import org.acegisecurity.userdetails.UserDetails;%0A
 import org.j
@@ -1259,16 +1259,73 @@
 ng name;
+%0A    private final GrantedAuthority%5B%5D grantedAuthorities;
 %0A%0A    pu
@@ -3956,24 +3956,25 @@
 tSubject();%0A
+%0A
             
@@ -4319,32 +4319,342 @@
               %7D%0A
+                    UserDetails d = Jenkins.getInstance().getSecurityRealm().loadUserByUsername(user.getId());%0A                    this.grantedAuthorities = d.getAuthorities();%0A                %7Delse%7B%0A                    this.grantedAuthorities = new GrantedAuthority%5B%5D %7Bnew GrantedAuthorityImpl(%22anonymous%22)%7D;%0A
                 
@@ -4692,16 +4692,17 @@
 ubject;%0A
+%0A
         
@@ -5370,24 +5370,62 @@
 ;%0A        %7D%0A
+        super.setAuthenticated(true);%0A
     %7D%0A%0A    @
@@ -5613,24 +5613,24 @@
 getName() %7B%0A
-
         retu
@@ -5643,11 +5643,116 @@
 e;%0A    %7D
+%0A%0A    @Override%0A    public GrantedAuthority%5B%5D getAuthorities() %7B%0A        return grantedAuthorities;%0A    %7D
 %0A%7D%0A

Fix for failure during permission check