/*
 * Copyright 2017-2018 Emmanuel Keller / Jaeksoft
 *  <p>
 *  Licensed 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
 *  <p>
 *  http://www.apache.org/licenses/LICENSE-2.0
 *  <p>
 *  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 com.jaeksoft.opensearchserver.services;

import com.jaeksoft.opensearchserver.model.ActiveStatus;
import com.jaeksoft.opensearchserver.model.UserRecord;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.qwazr.utils.LoggerUtils;
import com.qwazr.utils.StringUtils;
import com.qwazr.utils.concurrent.ThreadUtils;
import io.undertow.security.idm.Account;
import io.undertow.security.idm.Credential;

import javax.ws.rs.NotSupportedException;
import java.net.URI;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class JwtUsersService implements UsersService {

    private final static Logger LOGGER = LoggerUtils.getLogger(JwtUsersService.class);

    private final JWSSigner signer;
    private final String sharedSecret;
    private final URI jwtUri;

    private final Map<UUID, UserAccount> userAccounts;

    public JwtUsersService(ConfigService configService) throws KeyLengthException {
        sharedSecret =
            Objects.requireNonNull(configService.getOss2JwtKey(), "The oss2JwtKey configuration parameter is missing.");
        jwtUri =
            Objects.requireNonNull(configService.getOss2JwtUrl(), "The oss2JwtUrl configuration parameter is missing.");
        signer = new MACSigner(sharedSecret);
        userAccounts = new ConcurrentHashMap<>();
    }

    @Override
    public UserRecord getUserById(UUID userId) {
        return null;
    }

    @Override
    public boolean isSingleSignOn() {
        return true;
    }

    @Override
    public String getSingleSignOnRedirectUrl() {
        return jwtUri.toString();
    }

    @Override
    public Account verify(Account account) {
        if (!(account instanceof UserAccount))
            return null;
        return userAccounts.get(((UserAccount) account).getPrincipal().getId());
    }

    @Override
    public Account verify(String token, Credential credential) {
        if (StringUtils.isBlank(token))
            return null;
        try {

            final SignedJWT signedJWT = SignedJWT.parse(token);

            final JWSVerifier verifier = new MACVerifier(sharedSecret);

            if (!signedJWT.verify(verifier)) {
                ThreadUtils.sleep(2, TimeUnit.SECONDS);
                return null;
            }

            final JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();

            final Date expirationTime = claimsSet.getExpirationTime();
            if (expirationTime == null || new Date().after(expirationTime))
                return null;

            final String email = claimsSet.getStringClaim("email");
            final String name = claimsSet.getStringClaim("name");
            final String id = claimsSet.getStringClaim("id");
            if (StringUtils.isBlank(id))
                return null;

            final UserAccount userAccount = new UserAccount(new JwtUserRecord(UUID.fromString(id), name, email));
            userAccounts.put(userAccount.getPrincipal().getId(), userAccount);
            return userAccount;
        } catch (ParseException | JOSEException | IllegalArgumentException e) {
            LOGGER.log(Level.WARNING, e, e::getMessage);
            return null;
        }
    }

    @Override
    public Account verify(Credential credential) {
        return null;
    }

    private class JwtUserRecord implements UserRecord {

        private final UUID id;
        private final String name;
        private final String email;

        private JwtUserRecord(UUID id, String name, String email) {
            this.id = id;
            this.name = name;
            this.email = email;
        }

        @Override
        public UUID getId() {
            return id;
        }

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

        @Override
        public String getEmail() {
            return email;
        }

        @Override
        public ActiveStatus getStatus() {
            return ActiveStatus.ENABLED;
        }

        @Override
        public boolean matchPassword(String applicationSalt, String clearPassword) {
            throw new NotSupportedException("Matching password is not supported.");
        }

    }
}

@@ -1438,30 +1438,8 @@
 te;%0A
-import java.util.Map;%0A
 impo
@@ -1487,55 +1487,8 @@
 ID;%0A
-import java.util.concurrent.ConcurrentHashMap;%0A
 impo
@@ -1841,64 +1841,8 @@
 i;%0A%0A
-    private final Map%3CUUID, UserAccount%3E userAccounts;%0A%0A
     
@@ -2253,58 +2253,8 @@
 t);%0A
-        userAccounts = new ConcurrentHashMap%3C%3E();%0A
     
@@ -2602,151 +2602,22 @@
     
-if (!(account instanceof UserAccount))%0A            return null;%0A        return userAccounts.get(((UserAccount) account).getPrincipal().getId())
+return account
 ;%0A  
@@ -3598,39 +3598,14 @@
     
-final UserAccount userAccount =
+return
  new
@@ -3675,119 +3675,8 @@
 ));%0A
-            userAccounts.put(userAccount.getPrincipal().getId(), userAccount);%0A            return userAccount;%0A
     

Fix URI redirection issue