001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2015 Richard "Shred" Körber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j;
015
016import static java.util.stream.Collectors.toList;
017
018import java.net.URL;
019import java.time.Instant;
020import java.util.Collections;
021import java.util.List;
022
023import edu.umd.cs.findbugs.annotations.Nullable;
024import org.shredzone.acme4j.challenge.Challenge;
025import org.shredzone.acme4j.connector.Connection;
026import org.shredzone.acme4j.exception.AcmeException;
027import org.shredzone.acme4j.exception.AcmeProtocolException;
028import org.shredzone.acme4j.toolbox.AcmeUtils;
029import org.shredzone.acme4j.toolbox.JSON.Value;
030import org.shredzone.acme4j.toolbox.JSONBuilder;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034/**
035 * Represents an authorization request at the ACME server.
036 */
037public class Authorization extends AcmeJsonResource {
038    private static final long serialVersionUID = -3116928998379417741L;
039    private static final Logger LOG = LoggerFactory.getLogger(Authorization.class);
040
041    protected Authorization(Login login, URL location) {
042        super(login, location);
043    }
044
045    /**
046     * Gets the {@link Identifier} to be authorized.
047     * <p>
048     * For wildcard domain orders, the domain itself (without wildcard prefix) is returned
049     * here. To find out if this {@link Authorization} is related to a wildcard domain
050     * order, check the {@link #isWildcard()} method.
051     *
052     * @since 2.3
053     */
054    public Identifier getIdentifier() {
055        return getJSON().get("identifier").asIdentifier();
056    }
057
058    /**
059     * Gets the authorization status.
060     * <p>
061     * Possible values are: {@link Status#PENDING}, {@link Status#VALID},
062     * {@link Status#INVALID}, {@link Status#DEACTIVATED}, {@link Status#EXPIRED},
063     * {@link Status#REVOKED}.
064     */
065    public Status getStatus() {
066        return getJSON().get("status").asStatus();
067    }
068
069    /**
070     * Gets the expiry date of the authorization, if set by the server.
071     */
072    @Nullable
073    public Instant getExpires() {
074        return getJSON().get("expires")
075                    .map(Value::asString)
076                    .map(AcmeUtils::parseTimestamp)
077                    .orElse(null);
078    }
079
080    /**
081     * Returns {@code true} if this {@link Authorization} is related to a wildcard domain,
082     * {@code false} otherwise.
083     */
084    public boolean isWildcard() {
085        return getJSON().get("wildcard")
086                    .map(Value::asBoolean)
087                    .orElse(false);
088    }
089
090    /**
091     * Gets a list of all challenges offered by the server, in no specific order.
092     */
093    public List<Challenge> getChallenges() {
094        Login login = getLogin();
095
096        return Collections.unmodifiableList(getJSON().get("challenges")
097                    .asArray()
098                    .stream()
099                    .map(Value::asObject)
100                    .map(login::createChallenge)
101                    .collect(toList()));
102    }
103
104    /**
105     * Finds a {@link Challenge} of the given type. Responding to this {@link Challenge}
106     * is sufficient for authorization.
107     * <p>
108     * {@link Authorization#findChallenge(Class)} should be preferred, as this variant
109     * is not type safe.
110     *
111     * @param type
112     *            Challenge name (e.g. "http-01")
113     * @return {@link Challenge} matching that name, or {@code null} if there is no such
114     *         challenge, or if the challenge alone is not sufficient for authorization.
115     * @throws ClassCastException
116     *             if the type does not match the expected Challenge class type
117     */
118    @Nullable
119    public <T extends Challenge> T findChallenge(final String type) {
120        return (T) getChallenges().stream()
121                .filter(ch -> type.equals(ch.getType()))
122                .reduce((a, b) -> {throw new AcmeProtocolException("Found more than one challenge of type " + type);})
123                .orElse(null);
124    }
125
126    /**
127     * Finds a {@link Challenge} of the given class type. Responding to this {@link
128     * Challenge} is sufficient for authorization.
129     *
130     * @param type
131     *         Challenge type (e.g. "Http01Challenge.class")
132     * @return {@link Challenge} of that type, or {@code null} if there is no such
133     * challenge, or if the challenge alone is not sufficient for authorization.
134     * @since 2.8
135     */
136    @Nullable
137    public <T extends Challenge> T findChallenge(Class<T> type) {
138        return getChallenges().stream()
139                .filter(type::isInstance)
140                .map(type::cast)
141                .reduce((a, b) -> {throw new AcmeProtocolException("Found more than one challenge of type " + type.getName());})
142                .orElse(null);
143    }
144
145    /**
146     * Permanently deactivates the {@link Authorization}.
147     */
148    public void deactivate() throws AcmeException {
149        LOG.debug("deactivate");
150        try (Connection conn = getSession().connect()) {
151            JSONBuilder claims = new JSONBuilder();
152            claims.put("status", "deactivated");
153
154            conn.sendSignedRequest(getLocation(), claims, getLogin());
155            setJSON(conn.readJsonResponse());
156        }
157    }
158
159}