AssertionRequest.java

1
// Copyright (c) 2018, Yubico AB
2
// All rights reserved.
3
//
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
6
//
7
// 1. Redistributions of source code must retain the above copyright notice, this
8
//    list of conditions and the following disclaimer.
9
//
10
// 2. Redistributions in binary form must reproduce the above copyright notice,
11
//    this list of conditions and the following disclaimer in the documentation
12
//    and/or other materials provided with the distribution.
13
//
14
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25
package com.yubico.webauthn;
26
27
import com.fasterxml.jackson.annotation.JsonCreator;
28
import com.fasterxml.jackson.annotation.JsonProperty;
29
import com.fasterxml.jackson.core.JsonProcessingException;
30
import com.yubico.internal.util.JacksonCodecs;
31
import com.yubico.webauthn.data.ByteArray;
32
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
33
import java.util.Optional;
34
import lombok.Builder;
35
import lombok.NonNull;
36
import lombok.Value;
37
38
/**
39
 * A combination of a {@link PublicKeyCredentialRequestOptions} and, optionally, a {@link
40
 * #getUsername() username} or {@link #getUserHandle() user handle}.
41
 */
42
@Value
43
@Builder(toBuilder = true)
44
public class AssertionRequest {
45
46
  /**
47
   * An object that can be serialized to JSON and passed as the <code>publicKey</code> argument to
48
   * <code>navigator.credentials.get()</code>.
49
   */
50
  @NonNull private final PublicKeyCredentialRequestOptions publicKeyCredentialRequestOptions;
51
52
  /**
53
   * The username of the user to authenticate, if the user has already been identified.
54
   *
55
   * <p>This is mutually exclusive with {@link #getUserHandle() userHandle}; setting this will unset
56
   * {@link #getUserHandle() userHandle}. When parsing from JSON, {@link #getUserHandle()
57
   * userHandle} takes precedence over this.
58
   *
59
   * <p>If both this and {@link #getUserHandle() userHandle} are empty, this indicates that this is
60
   * a request for an assertion by a <a
61
   * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
62
   * credential</a> (passkey). Identification of the user is therefore deferred until the response
63
   * is received.
64
   *
65
   * @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
66
   *     href="https://passkeys.dev">passkeys.dev</a> reference
67
   */
68
  private final String username;
69
70
  /**
71
   * The user handle of the user to authenticate, if the user has already been identified.
72
   *
73
   * <p>This is mutually exclusive with {@link #getUsername() username}; setting this will unset
74
   * {@link #getUsername() username}. When parsing from JSON, this takes precedence over {@link
75
   * #getUsername() username}.
76
   *
77
   * <p>If both this and {@link #getUsername() username} are empty, this indicates that this is a
78
   * request for an assertion by a <a
79
   * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
80
   * credential</a> (passkey). Identification of the user is therefore deferred until the response
81
   * is received.
82
   *
83
   * @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
84
   *     href="https://passkeys.dev">passkeys.dev</a> reference
85
   */
86
  private final ByteArray userHandle;
87
88
  @JsonCreator
89
  private AssertionRequest(
90 1 1. <init> : negated conditional → KILLED
      @NonNull @JsonProperty("publicKeyCredentialRequestOptions")
91
          PublicKeyCredentialRequestOptions publicKeyCredentialRequestOptions,
92
      @JsonProperty("username") String username,
93
      @JsonProperty("userHandle") ByteArray userHandle) {
94
    this.publicKeyCredentialRequestOptions = publicKeyCredentialRequestOptions;
95
96 1 1. <init> : negated conditional → KILLED
    if (userHandle != null) {
97
      this.username = null;
98
      this.userHandle = userHandle;
99
    } else {
100
      this.username = username;
101
      this.userHandle = null;
102
    }
103
  }
104
105
  /**
106
   * The username of the user to authenticate, if the user has already been identified.
107
   *
108
   * <p>This is mutually exclusive with {@link #getUserHandle()}; if this is present, then {@link
109
   * #getUserHandle()} will be empty.
110
   *
111
   * <p>If both this and {@link #getUserHandle()} are empty, this indicates that this is a request
112
   * for an assertion by a <a
113
   * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
114
   * credential</a> (passkey). Identification of the user is therefore deferred until the response
115
   * is received.
116
   *
117
   * @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
118
   *     href="https://passkeys.dev">passkeys.dev</a> reference
119
   */
120
  public Optional<String> getUsername() {
121 1 1. getUsername : replaced return value with Optional.empty for com/yubico/webauthn/AssertionRequest::getUsername → KILLED
    return Optional.ofNullable(username);
122
  }
123
124
  /**
125
   * The user handle of the user to authenticate, if the user has already been identified.
126
   *
127
   * <p>This is mutually exclusive with {@link #getUsername()}; if this is present, then {@link
128
   * #getUsername()} will be empty.
129
   *
130
   * <p>If both this and {@link #getUsername()} are empty, this indicates that this is a request for
131
   * an assertion by a <a
132
   * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
133
   * credential</a> (passkey). Identification of the user is therefore deferred until the response
134
   * is received.
135
   *
136
   * @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
137
   *     href="https://passkeys.dev">passkeys.dev</a> reference
138
   */
139
  public Optional<ByteArray> getUserHandle() {
140 1 1. getUserHandle : replaced return value with Optional.empty for com/yubico/webauthn/AssertionRequest::getUserHandle → KILLED
    return Optional.ofNullable(userHandle);
141
  }
142
143
  /**
144
   * Serialize this {@link AssertionRequest} value to JSON suitable for sending to the client.
145
   *
146
   * <p>This is an alias of <code>getPublicKeyCredentialRequestOptions().toCredentialsGetJson()
147
   * </code>.
148
   *
149
   * <p>Any {@link ByteArray} values in this data structure will be {@link ByteArray#getBase64Url()
150
   * Base64Url} encoded. Those values MUST be decoded into <code>BufferSource</code> values (such as
151
   * <code>Uint8Array</code>) on the client side before calling <code>navigator.credentials.get()
152
   * </code>.
153
   *
154
   * <p>After decoding binary values, the resulting JavaScript object is suitable for passing as an
155
   * argument to <code>navigator.credentials.get()</code>.
156
   *
157
   * @return a JSON value suitable for sending to the client and passing as an argument to <code>
158
   *     navigator.credentials.get()</code>, after decoding binary options from Base64Url strings.
159
   * @throws JsonProcessingException if JSON serialization fails.
160
   */
161
  public String toCredentialsGetJson() throws JsonProcessingException {
162 1 1. toCredentialsGetJson : replaced return value with "" for com/yubico/webauthn/AssertionRequest::toCredentialsGetJson → KILLED
    return publicKeyCredentialRequestOptions.toCredentialsGetJson();
163
  }
164
165
  /**
166
   * Encode this {@link AssertionRequest} to JSON. The inverse of {@link #fromJson(String)}.
167
   *
168
   * <p>This method is suitable for encoding the {@link AssertionRequest} for temporary storage so
169
   * that it can later be passed as an argument to {@link
170
   * RelyingParty#finishAssertion(FinishAssertionOptions)}. The {@link #fromJson(String)} factory
171
   * function is guaranteed to restore an identical {@link AssertionRequest} instance.
172
   *
173
   * <p>Note that encoding might not be needed if you can simply keep the {@link AssertionRequest}
174
   * instance in server memory.
175
   *
176
   * @return this {@link AssertionRequest} encoded to JSON.
177
   * @throws JsonProcessingException
178
   */
179
  public String toJson() throws JsonProcessingException {
180 1 1. toJson : replaced return value with "" for com/yubico/webauthn/AssertionRequest::toJson → KILLED
    return JacksonCodecs.json().writeValueAsString(this);
181
  }
182
183
  /**
184
   * Decode an {@link AssertionRequest} from JSON. The inverse of {@link #toJson()}.
185
   *
186
   * <p>If the JSON was generated by the {@link #toJson()} method, then {@link #fromJson(String)} in
187
   * the same library version guarantees to restore an identical {@link AssertionRequest} instance.
188
   * This is not guaranteed between different library versions.
189
   *
190
   * @return a {@link AssertionRequest} decoded from the input JSON.
191
   * @throws JsonProcessingException
192
   */
193
  public static AssertionRequest fromJson(String json) throws JsonProcessingException {
194 1 1. fromJson : replaced return value with null for com/yubico/webauthn/AssertionRequest::fromJson → KILLED
    return JacksonCodecs.json().readValue(json, AssertionRequest.class);
195
  }
196
197
  public static AssertionRequestBuilder.MandatoryStages builder() {
198 1 1. builder : replaced return value with null for com/yubico/webauthn/AssertionRequest::builder → KILLED
    return new AssertionRequestBuilder.MandatoryStages();
199
  }
200
201
  public static class AssertionRequestBuilder {
202
    private String username = null;
203
    private ByteArray userHandle = null;
204
205
    public static class MandatoryStages {
206
      private final AssertionRequestBuilder builder = new AssertionRequestBuilder();
207
208
      /**
209
       * {@link
210
       * AssertionRequestBuilder#publicKeyCredentialRequestOptions(PublicKeyCredentialRequestOptions)
211
       * publicKeyCredentialRequestOptions} is a required parameter.
212
       *
213
       * @see
214
       *     AssertionRequestBuilder#publicKeyCredentialRequestOptions(PublicKeyCredentialRequestOptions)
215
       */
216
      public AssertionRequestBuilder publicKeyCredentialRequestOptions(
217
          PublicKeyCredentialRequestOptions publicKeyCredentialRequestOptions) {
218 1 1. publicKeyCredentialRequestOptions : replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder$MandatoryStages::publicKeyCredentialRequestOptions → KILLED
        return builder.publicKeyCredentialRequestOptions(publicKeyCredentialRequestOptions);
219
      }
220
    }
221
222
    /**
223
     * The username of the user to authenticate, if the user has already been identified.
224
     *
225
     * <p>This is mutually exclusive with {@link #userHandle(ByteArray)}; setting this to non-empty
226
     * will unset {@link #userHandle(ByteArray)}.
227
     *
228
     * <p>If this is empty, this indicates that this is a request for an assertion by a <a
229
     * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
230
     * credential</a> (passkey). Identification of the user is therefore deferred until the response
231
     * is received.
232
     *
233
     * @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
234
     *     href="https://passkeys.dev">passkeys.dev</a> reference
235
     */
236 1 1. username : negated conditional → KILLED
    public AssertionRequestBuilder username(@NonNull Optional<String> username) {
237 1 1. username : replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder::username → KILLED
      return this.username(username.orElse(null));
238
    }
239
240
    /**
241
     * The username of the user to authenticate, if the user has already been identified.
242
     *
243
     * <p>This is mutually exclusive with {@link #userHandle(ByteArray)}; setting this to non-<code>
244
     * null</code> will unset {@link #userHandle(ByteArray)}.
245
     *
246
     * <p>If this is empty, this indicates that this is a request for an assertion by a <a
247
     * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
248
     * credential</a> (passkey). Identification of the user is therefore deferred until the response
249
     * is received.
250
     *
251
     * @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
252
     *     href="https://passkeys.dev">passkeys.dev</a> reference
253
     */
254
    public AssertionRequestBuilder username(String username) {
255
      this.username = username;
256 1 1. username : negated conditional → KILLED
      if (username != null) {
257
        this.userHandle = null;
258
      }
259 1 1. username : replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder::username → KILLED
      return this;
260
    }
261
262
    /**
263
     * The user handle of the user to authenticate, if the user has already been identified.
264
     *
265
     * <p>This is mutually exclusive with {@link #username(String)}; setting this to non-empty will
266
     * unset {@link #username(String)}.
267
     *
268
     * <p>If both this and {@link #username(String)} are empty, this indicates that this is a
269
     * request for an assertion by a <a
270
     * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
271
     * credential</a> (passkey). Identification of the user is therefore deferred until the response
272
     * is received.
273
     *
274
     * @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
275
     *     href="https://passkeys.dev">passkeys.dev</a> reference
276
     */
277 1 1. userHandle : negated conditional → KILLED
    public AssertionRequestBuilder userHandle(@NonNull Optional<ByteArray> userHandle) {
278 1 1. userHandle : replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder::userHandle → KILLED
      return this.userHandle(userHandle.orElse(null));
279
    }
280
281
    /**
282
     * The user handle of the user to authenticate, if the user has already been identified.
283
     *
284
     * <p>This is mutually exclusive with {@link #username(String)}; setting this to non-<code>null
285
     * </code> will unset {@link #username(String)}.
286
     *
287
     * <p>If both this and {@link #username(String)} are empty, this indicates that this is a
288
     * request for an assertion by a <a
289
     * href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
290
     * credential</a> (passkey). Identification of the user is therefore deferred until the response
291
     * is received.
292
     *
293
     * @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
294
     *     href="https://passkeys.dev">passkeys.dev</a> reference
295
     */
296
    public AssertionRequestBuilder userHandle(ByteArray userHandle) {
297 1 1. userHandle : negated conditional → KILLED
      if (userHandle != null) {
298
        this.username = null;
299
      }
300
      this.userHandle = userHandle;
301 1 1. userHandle : replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder::userHandle → KILLED
      return this;
302
    }
303
  }
304
}

Mutations

90

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

96

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

121

1.1
Location : getUsername
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with Optional.empty for com/yubico/webauthn/AssertionRequest::getUsername → KILLED

140

1.1
Location : getUserHandle
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with Optional.empty for com/yubico/webauthn/AssertionRequest::getUserHandle → KILLED

162

1.1
Location : toCredentialsGetJson
Killed by : com.yubico.webauthn.data.JsonIoSpec
replaced return value with "" for com/yubico/webauthn/AssertionRequest::toCredentialsGetJson → KILLED

180

1.1
Location : toJson
Killed by : com.yubico.webauthn.data.JsonIoSpec
replaced return value with "" for com/yubico/webauthn/AssertionRequest::toJson → KILLED

194

1.1
Location : fromJson
Killed by : com.yubico.webauthn.data.JsonIoSpec
replaced return value with null for com/yubico/webauthn/AssertionRequest::fromJson → KILLED

198

1.1
Location : builder
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/AssertionRequest::builder → KILLED

218

1.1
Location : publicKeyCredentialRequestOptions
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder$MandatoryStages::publicKeyCredentialRequestOptions → KILLED

236

1.1
Location : username
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

237

1.1
Location : username
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder::username → KILLED

256

1.1
Location : username
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
negated conditional → KILLED

259

1.1
Location : username
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder::username → KILLED

277

1.1
Location : userHandle
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

278

1.1
Location : userHandle
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder::userHandle → KILLED

297

1.1
Location : userHandle
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

301

1.1
Location : userHandle
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/AssertionRequest$AssertionRequestBuilder::userHandle → KILLED

Active mutators

Tests examined


Report generated by PIT 1.15.0