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}