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 * Gets a list of all challenges offered by the server, in no specific order. 088 */ 089 public List<Challenge> getChallenges() { 090 var login = getLogin(); 091 092 return getJSON().get("challenges") 093 .asArray() 094 .stream() 095 .map(Value::asObject) 096 .map(login::createChallenge) 097 .collect(toUnmodifiableList()); 098 } 099 100 /** 101 * Finds a {@link Challenge} of the given type. Responding to this {@link Challenge} 102 * is sufficient for authorization. 103 * <p> 104 * {@link Authorization#findChallenge(Class)} should be preferred, as this variant 105 * is not type safe. 106 * 107 * @param type 108 * Challenge name (e.g. "http-01") 109 * @return {@link Challenge} matching that name, or empty if there is no such 110 * challenge, or if the challenge alone is not sufficient for authorization. 111 * @throws ClassCastException 112 * if the type does not match the expected Challenge class type 113 */ 114 @SuppressWarnings("unchecked") 115 public <T extends Challenge> Optional<T> findChallenge(final String type) { 116 return (Optional<T>) getChallenges().stream() 117 .filter(ch -> type.equals(ch.getType())) 118 .reduce((a, b) -> { 119 throw new AcmeProtocolException("Found more than one challenge of type " + type); 120 }); 121 } 122 123 /** 124 * Finds a {@link Challenge} of the given class type. Responding to this {@link 125 * Challenge} is sufficient for authorization. 126 * 127 * @param type 128 * Challenge type (e.g. "Http01Challenge.class") 129 * @return {@link Challenge} of that type, or empty if there is no such 130 * challenge, or if the challenge alone is not sufficient for authorization. 131 * @since 2.8 132 */ 133 public <T extends Challenge> Optional<T> findChallenge(Class<T> type) { 134 return getChallenges().stream() 135 .filter(type::isInstance) 136 .map(type::cast) 137 .reduce((a, b) -> { 138 throw new AcmeProtocolException("Found more than one challenge of type " + type.getName()); 139 }); 140 } 141 142 /** 143 * Permanently deactivates the {@link Authorization}. 144 */ 145 public void deactivate() throws AcmeException { 146 LOG.debug("deactivate"); 147 try (var conn = getSession().connect()) { 148 var claims = new JSONBuilder(); 149 claims.put("status", "deactivated"); 150 151 conn.sendSignedRequest(getLocation(), claims, getLogin()); 152 setJSON(conn.readJsonResponse()); 153 } 154 } 155 156}