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.toUnmodifiableList; 017 018import java.net.URL; 019import java.time.Instant; 020import java.util.List; 021import java.util.Optional; 022 023import org.shredzone.acme4j.challenge.Challenge; 024import org.shredzone.acme4j.exception.AcmeException; 025import org.shredzone.acme4j.exception.AcmeProtocolException; 026import org.shredzone.acme4j.toolbox.AcmeUtils; 027import org.shredzone.acme4j.toolbox.JSON.Value; 028import org.shredzone.acme4j.toolbox.JSONBuilder; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032/** 033 * Represents an authorization request at the ACME server. 034 */ 035public class Authorization extends AcmeJsonResource { 036 private static final long serialVersionUID = -3116928998379417741L; 037 private static final Logger LOG = LoggerFactory.getLogger(Authorization.class); 038 039 protected Authorization(Login login, URL location) { 040 super(login, location); 041 } 042 043 /** 044 * Gets the {@link Identifier} to be authorized. 045 * <p> 046 * For wildcard domain orders, the domain itself (without wildcard prefix) is returned 047 * here. To find out if this {@link Authorization} is related to a wildcard domain 048 * order, check the {@link #isWildcard()} method. 049 * 050 * @since 2.3 051 */ 052 public Identifier getIdentifier() { 053 return getJSON().get("identifier").asIdentifier(); 054 } 055 056 /** 057 * Gets the authorization status. 058 * <p> 059 * Possible values are: {@link Status#PENDING}, {@link Status#VALID}, 060 * {@link Status#INVALID}, {@link Status#DEACTIVATED}, {@link Status#EXPIRED}, 061 * {@link Status#REVOKED}. 062 */ 063 public Status getStatus() { 064 return getJSON().get("status").asStatus(); 065 } 066 067 /** 068 * Gets the expiry date of the authorization, if set by the server. 069 */ 070 public Optional<Instant> getExpires() { 071 return getJSON().get("expires") 072 .map(Value::asString) 073 .map(AcmeUtils::parseTimestamp); 074 } 075 076 /** 077 * Returns {@code true} if this {@link Authorization} is related to a wildcard domain, 078 * {@code false} otherwise. 079 */ 080 public boolean isWildcard() { 081 return getJSON().get("wildcard") 082 .map(Value::asBoolean) 083 .orElse(false); 084 } 085 086 /** 087 * Returns {@code true} if certificates for subdomains can be issued according to 088 * RFC9444. 089 * 090 * @since 3.3.0 091 */ 092 public boolean isSubdomainAuthAllowed() { 093 return getJSON().get("subdomainAuthAllowed") 094 .map(Value::asBoolean) 095 .orElse(false); 096 } 097 098 /** 099 * Gets a list of all challenges offered by the server, in no specific order. 100 */ 101 public List<Challenge> getChallenges() { 102 var login = getLogin(); 103 104 return getJSON().get("challenges") 105 .asArray() 106 .stream() 107 .map(Value::asObject) 108 .map(login::createChallenge) 109 .collect(toUnmodifiableList()); 110 } 111 112 /** 113 * Finds a {@link Challenge} of the given type. Responding to this {@link Challenge} 114 * is sufficient for authorization. 115 * <p> 116 * {@link Authorization#findChallenge(Class)} should be preferred, as this variant 117 * is not type safe. 118 * 119 * @param type 120 * Challenge name (e.g. "http-01") 121 * @return {@link Challenge} matching that name, or empty if there is no such 122 * challenge, or if the challenge alone is not sufficient for authorization. 123 * @throws ClassCastException 124 * if the type does not match the expected Challenge class type 125 */ 126 @SuppressWarnings("unchecked") 127 public <T extends Challenge> Optional<T> findChallenge(final String type) { 128 return (Optional<T>) getChallenges().stream() 129 .filter(ch -> type.equals(ch.getType())) 130 .reduce((a, b) -> { 131 throw new AcmeProtocolException("Found more than one challenge of type " + type); 132 }); 133 } 134 135 /** 136 * Finds a {@link Challenge} of the given class type. Responding to this {@link 137 * Challenge} is sufficient for authorization. 138 * 139 * @param type 140 * Challenge type (e.g. "Http01Challenge.class") 141 * @return {@link Challenge} of that type, or empty if there is no such 142 * challenge, or if the challenge alone is not sufficient for authorization. 143 * @since 2.8 144 */ 145 public <T extends Challenge> Optional<T> findChallenge(Class<T> type) { 146 return getChallenges().stream() 147 .filter(type::isInstance) 148 .map(type::cast) 149 .reduce((a, b) -> { 150 throw new AcmeProtocolException("Found more than one challenge of type " + type.getName()); 151 }); 152 } 153 154 /** 155 * Permanently deactivates the {@link Authorization}. 156 */ 157 public void deactivate() throws AcmeException { 158 LOG.debug("deactivate"); 159 try (var conn = getSession().connect()) { 160 var claims = new JSONBuilder(); 161 claims.put("status", "deactivated"); 162 163 conn.sendSignedRequest(getLocation(), claims, getLogin()); 164 setJSON(conn.readJsonResponse()); 165 } 166 } 167 168}