001/*
002 * Copyright 2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk;
037
038
039
040import java.util.ArrayList;
041import java.util.List;
042
043import com.unboundid.asn1.ASN1OctetString;
044import com.unboundid.util.ByteStringBuffer;
045import com.unboundid.util.Debug;
046import com.unboundid.util.NotMutable;
047import com.unboundid.util.NotNull;
048import com.unboundid.util.Nullable;
049import com.unboundid.util.StaticUtils;
050import com.unboundid.util.ThreadSafety;
051import com.unboundid.util.ThreadSafetyLevel;
052import com.unboundid.util.Validator;
053
054
055
056/**
057 * This class provides an implementation of a SASL bind request that uses the
058 * OAUTHBEARER SASL mechanism described in
059 * <A HREF="http://www.ietf.org/rfc/rfc7628.txt">RFC 7628</A> to allow a user
060 * to authenticate with an OAuth 2.0 bearer token.
061 *
062 * @see  OAUTHBEARERBindRequestProperties
063 * @see  OAUTHBEARERBindResult
064 */
065@NotMutable()
066@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
067public final class OAUTHBEARERBindRequest
068       extends SASLBindRequest
069{
070  /**
071   * The name for the OAUTHBEARER SASL mechanism.
072   */
073  @NotNull public static final String OAUTHBEARER_MECHANISM_NAME =
074       "OAUTHBEARER";
075
076
077
078  /**
079   * The delimiter that appears between elements of the GS2 header.
080   */
081  private static final byte GS2_HEADER_DELIMITER = ',';
082
083
084
085  /**
086   * The delimiter that appears after each element of the encoded credentials.
087   */
088  private static final byte OAUTHBEARER_DELIMITER = (byte) 0x01;
089
090
091
092  /**
093   * The component of the GS2 header that indicates that channel binding is not
094   * supported.
095   */
096  @NotNull private static final byte[] GS2_HEADER_ELEMENT_NO_CHANNEL_BINDING =
097       StaticUtils.getBytes("n");
098
099
100
101  /**
102   * The component of the GS2 header that precedes the authorization ID.
103   */
104  @NotNull private static final byte[] GS2_HEADER_ELEMENT_AUTHZ_ID_PREFIX =
105       StaticUtils.getBytes("a=");
106
107
108
109  /**
110   * The component of the OAUTHBEARER bind request credentials that precedes the
111   * access token.
112   */
113  @NotNull private static final byte[]
114       OAUTHBEARER_CRED_ELEMENT_ACCESS_TOKEN_PREFIX =
115            StaticUtils.getBytes("auth=Bearer ");
116
117
118
119  /**
120   * The component of the OAUTHBEARER bind request credentials that precedes the
121   * server address.
122   */
123  @NotNull private static final byte[]
124       OAUTHBEARER_CRED_ELEMENT_SERVER_ADDRESS_PREFIX =
125            StaticUtils.getBytes("host=");
126
127
128
129  /**
130   * The component of the OAUTHBEARER bind request credentials that precedes the
131   * server port.
132   */
133  @NotNull private static final byte[]
134       OAUTHBEARER_CRED_ELEMENT_SERVER_PORT_PREFIX =
135            StaticUtils.getBytes("port=");
136
137
138
139  /**
140   * The component of the OAUTHBEARER bind request credentials that precedes the
141   * request method.
142   */
143  @NotNull private static final byte[]
144       OAUTHBEARER_CRED_ELEMENT_REQUEST_METHOD_PREFIX =
145            StaticUtils.getBytes("mthd=");
146
147
148
149  /**
150   * The component of the OAUTHBEARER bind request credentials that precedes the
151   * request path.
152   */
153  @NotNull private static final byte[]
154       OAUTHBEARER_CRED_ELEMENT_REQUEST_PATH_PREFIX =
155            StaticUtils.getBytes("path=");
156
157
158
159  /**
160   * The component of the OAUTHBEARER bind request credentials that precedes the
161   * request post data.
162   */
163  @NotNull private static final byte[]
164       OAUTHBEARER_CRED_ELEMENT_REQUEST_POST_DATA_PREFIX =
165            StaticUtils.getBytes("post=");
166
167
168
169  /**
170   * The component of the OAUTHBEARER bind request credentials that precedes the
171   * request query string.
172   */
173  @NotNull private static final byte[]
174       OAUTHBEARER_CRED_ELEMENT_REQUEST_QUERY_STRING_PREFIX =
175            StaticUtils.getBytes("qs=");
176
177
178
179  /**
180   * The SASL credentials that should be included in the dummy bind request that
181   * is used to complete a failed authentication attempt.
182   */
183  @NotNull private static final ASN1OctetString DUMMY_REQUEST_CREDENTIALS =
184       new ASN1OctetString(new byte[] { OAUTHBEARER_DELIMITER });
185
186
187
188  /**
189   * The serial version UID for this serializable class.
190   */
191  private static final long serialVersionUID = -1216152242833705618L;
192
193
194
195  // The message ID from the last LDAP message sent from this request.
196  private volatile int messageID;
197
198  // The port of the server to which the request will be sent.
199  @Nullable private final Integer serverPort;
200
201  // The access token to include in the bind request.
202  @NotNull private final String accessToken;
203
204  // The authorization identity to include in the GS2 header for the bind
205  // request.
206  @Nullable private final String authorizationID;
207
208  // The method to use for HTTP-based requests.
209  @Nullable private final String requestMethod;
210
211  // The path to use for HTTP-based requests.
212  @Nullable private final String requestPath;
213
214  // The post data for HTTP-based requests.
215  @Nullable private final String requestPostData;
216
217  // The query string for HTTP-based requests.
218  @Nullable private final String requestQueryString;
219
220  // The address of the server to which the request will be sent.
221  @Nullable private final String serverAddress;
222
223
224
225  /**
226   * Creates a new OAUTHBEARER bind request with the provided access token.
227   * All other properties will be unset.
228   *
229   * @param  accessToken  The access token to use for this bind request.  It
230   *                      must not be {@code null} or empty.
231   * @param  controls     The set of controls to include in the bind request.
232   *                      It may be {@code null} or empty if no controls are
233   *                      needed.
234   */
235  public OAUTHBEARERBindRequest(@NotNull final String accessToken,
236                                @Nullable final Control... controls)
237  {
238    super(controls);
239
240    Validator.ensureNotNullOrEmpty(accessToken,
241         "OAUTHBEARERBindRequest.accessToken must not be null or empty.");
242
243    this.accessToken = accessToken;
244
245    authorizationID = null;
246    serverAddress = null;
247    serverPort = null;
248    requestMethod = null;
249    requestPath = null;
250    requestPostData = null;
251    requestQueryString = null;
252
253    messageID = -1;
254  }
255
256
257
258  /**
259   * Creates a new OAUTHBEARER bind request with the provided set of properties.
260   *
261   * @param  properties  The set of properties to use to create this bind
262   *                     request.  It must not be {@code null}.
263   * @param  controls    The set of controls to include in the bind request.  It
264   *                     may be {@code null} or empty if no controls are needed.
265   */
266  public OAUTHBEARERBindRequest(
267              @NotNull final OAUTHBEARERBindRequestProperties properties,
268              @Nullable final Control... controls)
269  {
270    super(controls);
271
272    accessToken = properties.getAccessToken();
273    authorizationID = properties.getAuthorizationID();
274    serverAddress = properties.getServerAddress();
275    serverPort = properties.getServerPort();
276    requestMethod = properties.getRequestMethod();
277    requestPath = properties.getRequestPath();
278    requestPostData = properties.getRequestPostData();
279    requestQueryString = properties.getRequestQueryString();
280
281    messageID = -1;
282  }
283
284
285
286  /**
287   * {@inheritDoc}
288   */
289  @Override()
290  @NotNull()
291  public String getSASLMechanismName()
292  {
293    return OAUTHBEARER_MECHANISM_NAME;
294  }
295
296
297
298  /**
299   * Retrieves the access token to include in the bind request.
300   *
301   * @return  The access token to include in the bind request.
302   */
303  @NotNull()
304  public String getAccessToken()
305  {
306    return accessToken;
307  }
308
309
310
311  /**
312   * Retrieves the authorization ID to include in the GS2 header for the bind
313   * request, if any.
314   *
315   * @return  The authorization ID to include in the GS2 header for the bind
316   *          request, or {@code null} if no authorization ID should be
317   *          included.
318   */
319  @Nullable()
320  public String getAuthorizationID()
321  {
322    return authorizationID;
323  }
324
325
326
327  /**
328   * Retrieves the server address to include in the bind request, if any.
329   *
330   * @return  The server address to include in the bind request, or {@code null}
331   *          if it should be omitted.
332   */
333  @Nullable()
334  public String getServerAddress()
335  {
336    return serverAddress;
337  }
338
339
340
341  /**
342   * Retrieves the server port to include in the bind request, if any.
343   *
344   * @return  The server port to include in the bind request, or {@code null}
345   *          if it should be omitted.
346   */
347  @Nullable()
348  public Integer getServerPort()
349  {
350    return serverPort;
351  }
352
353
354
355  /**
356   * Retrieves the method to use for HTTP-based requests, if any.
357   *
358   * @return  The method to use for HTTP-based requests, or {@code null} if it
359   *          should be omitted from the bind request.
360   */
361  @Nullable()
362  public String getRequestMethod()
363  {
364    return requestMethod;
365  }
366
367
368
369  /**
370   * Retrieves the path to use for HTTP-based requests, if any.
371   *
372   * @return  The path to use for HTTP-based requests, or {@code null} if it
373   *          should be omitted from the bind request.
374   */
375  @Nullable()
376  public String getRequestPath()
377  {
378    return requestPath;
379  }
380
381
382
383  /**
384   * Retrieves the data to submit when posting an HTTP-based request, if any.
385   *
386   * @return  The post data for HTTP-based requests, or {@code null} if it
387   *          should be omitted from the bind request.
388   */
389  @Nullable()
390  public String getRequestPostData()
391  {
392    return requestPostData;
393  }
394
395
396
397  /**
398   * Retrieves the query string to use for HTTP-based requests, if any.
399   *
400   * @return  The query string to use for HTTP-based requests, or {@code null}
401   *          if it should be omitted from the bind request.
402   */
403  @Nullable()
404  public String getRequestQueryString()
405  {
406    return requestQueryString;
407  }
408
409
410
411  /**
412   * {@inheritDoc}
413   */
414  @Override()
415  @NotNull()
416  protected OAUTHBEARERBindResult process(
417                 @NotNull final LDAPConnection connection, final int depth)
418            throws LDAPException
419  {
420    // Send the initial request.  If the response has a result code that is
421    // anything other than SASL_BIND_IN_PROGRESS, then we can just return it
422    // directly without needing to do anything else.
423    messageID = InternalSDKHelper.nextMessageID(connection);
424    final BindResult initialBindResult =  sendBindRequest(connection, "",
425         encodeCredentials(), getControls(),
426         getResponseTimeoutMillis(connection));
427    if (initialBindResult.getResultCode() != ResultCode.SASL_BIND_IN_PROGRESS)
428    {
429      return new OAUTHBEARERBindResult(initialBindResult);
430    }
431
432
433    // If we've gotten here, then it indicates that the attempt failed.  We need
434    // to send a second, dummy request to complete the bind process and get the
435    // ultimate failure result.
436    BindResult finalBindResult;
437    try
438    {
439      messageID = InternalSDKHelper.nextMessageID(connection);
440      finalBindResult = sendBindRequest(connection, "",
441           DUMMY_REQUEST_CREDENTIALS, getControls(),
442           getResponseTimeoutMillis(connection));
443    }
444    catch (final LDAPException e)
445    {
446      Debug.debugException(e);
447      finalBindResult = new BindResult(e);
448    }
449
450    return new OAUTHBEARERBindResult(initialBindResult, finalBindResult);
451  }
452
453
454
455  /**
456   * Encodes the credentials as appropriate for this bind request.
457   *
458   * @return  An ASN.1 octet string containing the encoded credentials.
459   */
460  @NotNull()
461  ASN1OctetString encodeCredentials()
462  {
463    final ByteStringBuffer buffer = new ByteStringBuffer();
464
465    // Construct the GS2 header and follow it with the necessary delimiter.
466    buffer.append(GS2_HEADER_ELEMENT_NO_CHANNEL_BINDING);
467    buffer.append(GS2_HEADER_DELIMITER);
468
469    if (authorizationID != null)
470    {
471      buffer.append(GS2_HEADER_ELEMENT_AUTHZ_ID_PREFIX);
472      escapeAuthorizationID(authorizationID, buffer);
473    }
474
475    buffer.append(GS2_HEADER_DELIMITER);
476    buffer.append(OAUTHBEARER_DELIMITER);
477
478
479    // Append the access token.
480    buffer.append(OAUTHBEARER_CRED_ELEMENT_ACCESS_TOKEN_PREFIX);
481    buffer.append(accessToken);
482    buffer.append(OAUTHBEARER_DELIMITER);
483
484
485    // Append the server address, if appropriate.
486    if (serverAddress != null)
487    {
488      buffer.append(OAUTHBEARER_CRED_ELEMENT_SERVER_ADDRESS_PREFIX);
489      buffer.append(serverAddress);
490      buffer.append(OAUTHBEARER_DELIMITER);
491    }
492
493
494    // Append the server port, if appropriate.
495    if (serverPort != null)
496    {
497      buffer.append(OAUTHBEARER_CRED_ELEMENT_SERVER_PORT_PREFIX);
498      buffer.append(serverPort.toString());
499      buffer.append(OAUTHBEARER_DELIMITER);
500    }
501
502
503    // Append the request method, if appropriate.
504    if (requestMethod != null)
505    {
506      buffer.append(OAUTHBEARER_CRED_ELEMENT_REQUEST_METHOD_PREFIX);
507      buffer.append(requestMethod);
508      buffer.append(OAUTHBEARER_DELIMITER);
509    }
510
511
512    // Append the request path, if appropriate.
513    if (requestPath != null)
514    {
515      buffer.append(OAUTHBEARER_CRED_ELEMENT_REQUEST_PATH_PREFIX);
516      buffer.append(requestPath);
517      buffer.append(OAUTHBEARER_DELIMITER);
518    }
519
520
521    // Append the request post data, if appropriate.
522    if (requestPostData != null)
523    {
524      buffer.append(OAUTHBEARER_CRED_ELEMENT_REQUEST_POST_DATA_PREFIX);
525      buffer.append(requestPostData);
526      buffer.append(OAUTHBEARER_DELIMITER);
527    }
528
529
530    // Append the request query string, if appropriate.
531    if (requestQueryString != null)
532    {
533      buffer.append(OAUTHBEARER_CRED_ELEMENT_REQUEST_QUERY_STRING_PREFIX);
534      buffer.append(requestQueryString);
535      buffer.append(OAUTHBEARER_DELIMITER);
536    }
537
538    return new ASN1OctetString(buffer.toByteArray());
539  }
540
541
542
543  /**
544   * Appends an escaped version of the provided authorization ID to the given
545   * buffer.  Any equal signs will be replaced with "=3D" and any commas will be
546   * replaced with "=2C".
547   *
548   * @param  authorizationID  The authorization ID to be escaped.
549   * @param  buffer           The buffer to which the escaped authorization ID
550   *                          should be appended.
551   */
552  private static void escapeAuthorizationID(
553               @NotNull final String authorizationID,
554               @NotNull final ByteStringBuffer buffer)
555  {
556    final int length = authorizationID.length();
557    for (int i=0; i < length; i++)
558    {
559      final char c = authorizationID.charAt(i);
560      switch (c)
561      {
562        case ',':
563          buffer.append("=2C");
564          break;
565        case '=':
566          buffer.append("=3D");
567          break;
568        default:
569          buffer.append(c);
570          break;
571      }
572    }
573  }
574
575
576
577  /**
578   * {@inheritDoc}
579   */
580  @Override()
581  @NotNull()
582  public OAUTHBEARERBindRequest duplicate()
583  {
584    return duplicate(getControls());
585  }
586
587
588
589  /**
590   * {@inheritDoc}
591   */
592  @Override()
593  @NotNull()
594  public OAUTHBEARERBindRequest duplicate(@Nullable final Control[] controls)
595  {
596    final OAUTHBEARERBindRequestProperties properties =
597         new OAUTHBEARERBindRequestProperties(this);
598    final OAUTHBEARERBindRequest bindRequest =
599         new OAUTHBEARERBindRequest(properties, controls);
600    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
601    return bindRequest;
602  }
603
604
605
606  /**
607   * {@inheritDoc}
608   */
609  @Override()
610  public int getLastMessageID()
611  {
612    return messageID;
613  }
614
615
616
617  /**
618   * Retrieves a string representation of the OAUTHBEARER bind request.
619   *
620   * @return  A string representation of the OAUTHBEARER bind request.
621   */
622  @Override()
623  @NotNull()
624  public String toString()
625  {
626    final StringBuilder buffer = new StringBuilder();
627    toString(buffer);
628    return buffer.toString();
629  }
630
631
632
633  /**
634   * Appends a string representation of the OAUTHBEARER bind request to the
635   * provided buffer.
636   *
637   * @param  buffer  The buffer to which the information should be appended.  It
638   *                 must not be {@code null}.
639   */
640  @Override()
641  public void toString(@NotNull final StringBuilder buffer)
642  {
643    buffer.append("OAUTHBEARERBindRequest(accessToken='{redacted}'");
644
645    if (authorizationID != null)
646    {
647      buffer.append(", authorizationID='");
648      buffer.append(authorizationID);
649      buffer.append('\'');
650    }
651
652    if (serverAddress != null)
653    {
654      buffer.append(", serverAddress='");
655      buffer.append(serverAddress);
656      buffer.append('\'');
657    }
658
659    if (serverPort != null)
660    {
661      buffer.append(", serverPort=");
662      buffer.append(serverPort);
663    }
664
665    if (requestMethod != null)
666    {
667      buffer.append(", requestMethod='");
668      buffer.append(requestMethod);
669      buffer.append('\'');
670    }
671
672    if (requestPath != null)
673    {
674      buffer.append(", requestPath='");
675      buffer.append(requestPath);
676      buffer.append('\'');
677    }
678
679    if (requestPostData != null)
680    {
681      buffer.append(", requestPostData='{redacted}'");
682    }
683
684    if (requestQueryString != null)
685    {
686      buffer.append(", requestQueryString='");
687      buffer.append(requestQueryString);
688      buffer.append('\'');
689    }
690
691    buffer.append(')');
692  }
693
694
695
696  /**
697   * {@inheritDoc}
698   */
699  @Override()
700  public void toCode(@NotNull final List<String> lineList,
701                     @NotNull final String requestID,
702                     final int indentSpaces, final boolean includeProcessing)
703  {
704    // Create and update the request properties object.
705    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
706         "OAUTHBEARERBindRequestProperties", requestID + "RequestProperties",
707         "new OAUTHBEARERBindRequestProperties",
708         ToCodeArgHelper.createString(accessToken, "Access Token"));
709
710    if (authorizationID != null)
711    {
712      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
713           requestID + "RequestProperties.setAuthorizationID",
714           ToCodeArgHelper.createString(authorizationID, null));
715    }
716
717    if (serverAddress != null)
718    {
719      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
720           requestID + "RequestProperties.setServerAddress",
721           ToCodeArgHelper.createString(serverAddress, null));
722    }
723
724    if (serverPort != null)
725    {
726      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
727           requestID + "RequestProperties.setServerPort",
728           ToCodeArgHelper.createInteger(serverPort, null));
729    }
730
731    if (requestMethod != null)
732    {
733      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
734           requestID + "RequestProperties.setRequestMethod",
735           ToCodeArgHelper.createString(requestMethod, null));
736    }
737
738    if (requestPath != null)
739    {
740      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
741           requestID + "RequestProperties.setRequestPath",
742           ToCodeArgHelper.createString(requestPath, null));
743    }
744
745    if (requestPostData != null)
746    {
747      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
748           requestID + "RequestProperties.setRequestPostData",
749           ToCodeArgHelper.createString(requestPostData, null));
750    }
751
752    if (requestQueryString != null)
753    {
754      ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null,
755           requestID + "RequestProperties.setRequestQueryString",
756           ToCodeArgHelper.createString(requestQueryString, null));
757    }
758
759
760    // Create the request variable.
761    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(2);
762    constructorArgs.add(
763         ToCodeArgHelper.createRaw(requestID + "RequestProperties", null));
764
765    final Control[] controls = getControls();
766    if (controls.length > 0)
767    {
768      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
769           "Bind Controls"));
770    }
771
772    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
773         "OAUTHBEARERBindRequest", requestID + "Request",
774         "new OAUTHBEARERBindRequest", constructorArgs);
775
776
777    // Add lines for processing the request and obtaining the result.
778    if (includeProcessing)
779    {
780      // Generate a string with the appropriate indent.
781      final StringBuilder buffer = new StringBuilder();
782      for (int i=0; i < indentSpaces; i++)
783      {
784        buffer.append(' ');
785      }
786      final String indent = buffer.toString();
787
788      lineList.add("");
789      lineList.add(indent + "try");
790      lineList.add(indent + '{');
791      lineList.add(indent + "  BindResult " + requestID +
792           "Result = connection.bind(" + requestID + "Request);");
793      lineList.add(indent + "  // The bind was processed successfully.");
794      lineList.add(indent + '}');
795      lineList.add(indent + "catch (LDAPException e)");
796      lineList.add(indent + '{');
797      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
798           "help explain why.");
799      lineList.add(indent + "  // Note that the connection is now likely in " +
800           "an unauthenticated state.");
801      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
802      lineList.add(indent + "  String message = e.getMessage();");
803      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
804      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
805      lineList.add(indent + "  Control[] responseControls = " +
806           "e.getResponseControls();");
807
808      lineList.add("");
809      lineList.add("OAUTHBEARERBindResult bindResult = " +
810                "new OAUTHBEARERBindResult(new BindResult(e));");
811      lineList.add("String authorizationErrorCode = " +
812           "bindResult.getAuthorizationErrorCode();");
813      lineList.add("Set<String> scopes = bindResult.getScopes();");
814      lineList.add("String openIDConfigurationURL = " +
815           "bindResult.getOpenIDConfigurationURL();");
816
817      lineList.add(indent + '}');
818    }
819  }
820}