/*
 * 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.guacamole.auth.cas.ticket;

import com.google.inject.Inject;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.xml.bind.DatatypeConverter;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.cas.conf.ConfigurationService;
import org.apache.guacamole.net.auth.Credentials;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
import org.jasig.cas.client.validation.TicketValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Service for validating ID tickets forwarded to us by the client, verifying
 * that they did indeed come from the CAS service.
 */
public class TicketValidationService {

    /**
     * Logger for this class.
     */
    private static final Logger logger = LoggerFactory.getLogger(TicketValidationService.class);

    /**
     * Service for retrieving CAS configuration information.
     */
    @Inject
    private ConfigurationService confService;

    /**
     * Validates and parses the given ID ticket, returning the username
     * provided by the CAS server in the ticket.  If the
     * ticket is invalid an exception is thrown.
     *
     * @param ticket
     *     The ID ticket to validate and parse.
     *
     * @return
     *     The username derived from the ticket.
     *
     * @throws GuacamoleException
     *     If the ID ticket is not valid or guacamole.properties could
     *     not be parsed.
     */
    public String validateTicket(String ticket, Credentials credentials) throws GuacamoleException {

        // Retrieve the configured CAS URL, establish a ticket validator,
        // and then attempt to validate the supplied ticket.  If that succeeds,
        // grab the principal returned by the validator.
        String casServerUrl = confService.getAuthorizationEndpoint();
        Cas20ProxyTicketValidator validator = new Cas20ProxyTicketValidator(casServerUrl);
        validator.setAcceptAnyProxy(true);
        try {
            String confRedirectURI = confService.getRedirectURI();
            Assertion a = validator.validate(ticket, confRedirectURI);
            AttributePrincipal principal =  a.getPrincipal();

            // Retrieve username and set the credentials.
            String username = principal.getName();
            if (username != null)
                credentials.setUsername(username);

            // Retrieve password, attempt decryption, and set credentials.
            Object credObj = principal.getAttributes().get("credential");
            if (credObj != null) {
                String clearPass = decryptPassword(credObj.toString());
                if (clearPass != null && !clearPass.isEmpty())
                    credentials.setPassword(clearPass);
            }

            return username;

        } 
        catch (TicketValidationException e) {
            throw new GuacamoleException("Ticket validation failed.", e);
        }

    }

    /**
     * Takes an encrypted string representing a password provided by
     * the CAS ClearPass service and decrypts it using the private
     * key configured for this extension.  Returns null if it is
     * unable to decrypt the password.
     *
     * @param encryptedPassword
     *     A string with the encrypted password provided by the
     *     CAS service.
     *
     * @return
     *     The decrypted password, or null if it is unable to
     *     decrypt the password.
     *
     * @throws GuacamoleException
     *     If unable to get Guacamole configuration data
     */
    private final String decryptPassword(String encryptedPassword)
            throws GuacamoleException {

        // If we get nothing, we return nothing.
        if (encryptedPassword == null || encryptedPassword.isEmpty()) {
            logger.warn("No or empty encrypted password, no password will be available.");
            return null;
        }

        final PrivateKey clearpassKey = confService.getClearpassKey();
        if (clearpassKey == null) {
            logger.warn("No private key available to decrypt password.");
            return null;
        }

        try {

            final Cipher cipher = Cipher.getInstance(clearpassKey.getAlgorithm());

            if (cipher == null)
                throw new GuacamoleServerException("Failed to initialize cipher object with private key.");

            // Initialize the Cipher in decrypt mode.
            cipher.init(Cipher.DECRYPT_MODE, clearpassKey);

            // Decode and decrypt, and return a new string.
            final byte[] pass64 = DatatypeConverter.parseBase64Binary(encryptedPassword);
            final byte[] cipherData = cipher.doFinal(pass64);
            return new String(cipherData);

        }
        catch (BadPaddingException e) {
            throw new GuacamoleServerException("Bad padding when decrypting cipher data.", e);
        }
        catch (IllegalBlockSizeException e) {
            throw new GuacamoleServerException("Illegal block size while opening private key.", e);
        }
        catch (InvalidKeyException e) {
            throw new GuacamoleServerException("Specified private key for ClearPass decryption is invalid.", e);
        }
        catch (NoSuchAlgorithmException e) {
            throw new GuacamoleServerException("Unexpected algorithm for the private key.", e);
        }
        catch (NoSuchPaddingException e) {
            throw new GuacamoleServerException("No such padding tryingto initialize cipher with private key.", e);
        }

    }

}

@@ -2443,16 +2443,139 @@
  parse.%0A
+     * @param credentials%0A     *     The Credentials object to store retrieved username and%0A     *     password values in.%0A
      *%0A 

Add missing parameter documentation for credentials object.