// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef DEVICE_FIDO_MAC_OPERATION_BASE_H_
#define DEVICE_FIDO_MAC_OPERATION_BASE_H_

#import <Foundation/Foundation.h>
#import <Security/Security.h>

#include "base/bind.h"
#include "base/callback.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/macros.h"
#include "base/strings/sys_string_conversions.h"
#include "device/fido/mac/credential_metadata.h"
#include "device/fido/mac/operation.h"
#include "device/fido/mac/touch_id_context.h"

namespace device {
namespace fido {
namespace mac {

// OperationBase abstracts behavior common to both concrete Operations,
// |MakeCredentialOperation| and |GetAssertionOperation|.
template <class Request, class Response>
class API_AVAILABLE(macosx(10.12.2)) OperationBase : public Operation {
 public:
  using Callback = base::OnceCallback<void(CtapDeviceResponseCode,
                                           base::Optional<Response>)>;

  OperationBase(Request request,
                std::string metadata_secret,
                std::string keychain_access_group,
                Callback callback)
      : request_(std::move(request)),
        metadata_secret_(std::move(metadata_secret)),
        keychain_access_group_(std::move(keychain_access_group)),
        callback_(std::move(callback)),
        touch_id_context_(std::make_unique<TouchIdContext>()) {}

  ~OperationBase() override = default;

 protected:
  // Subclasses must call Init() at the beginning of Run().
  bool Init() {
    base::Optional<std::string> encoded_rp_id =
        CredentialMetadata::EncodeRpId(metadata_secret(), RpId());
    if (!encoded_rp_id)
      return false;

    encoded_rp_id_ = std::move(*encoded_rp_id);
    return true;
  }

  // PromptTouchId triggers a Touch ID consent dialog with the given reason
  // string. Subclasses implement the PromptTouchIdDone callback to receive the
  // result.
  void PromptTouchId(std::string reason) {
    // The callback passed to TouchIdContext::Prompt will not fire if the
    // TouchIdContext itself has been deleted. Since that it is owned by this
    // class, there is no need to bind the callback to a weak ref here.
    touch_id_context_->PromptTouchId(
        std::move(reason), base::BindOnce(&OperationBase::PromptTouchIdDone,
                                          base::Unretained(this)));
  }

  // Callback for |PromptTouchId|.
  virtual void PromptTouchIdDone(bool success) = 0;

  // Subclasses override RpId to return the RP ID from the type-specific
  // request.
  virtual const std::string& RpId() const = 0;

  LAContext* authentication_context() const {
    return touch_id_context_->authentication_context();
  }
  SecAccessControlRef access_control() const {
    return touch_id_context_->access_control();
  }

  // DefaultKeychainQuery returns a default keychain query dictionary that has
  // the keychain item class, keychain access group and RP ID filled out (but
  // not the credential ID). More fields can be set on the return value to
  // refine the query.
  base::ScopedCFTypeRef<CFMutableDictionaryRef> DefaultKeychainQuery() const {
    base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
        CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
    CFDictionarySetValue(query, kSecClass, kSecClassKey);
    CFDictionarySetValue(query, kSecAttrAccessGroup,
                         base::SysUTF8ToNSString(keychain_access_group_));
    DCHECK(!encoded_rp_id_.empty());
    CFDictionarySetValue(query, kSecAttrLabel,
                         base::SysUTF8ToNSString(encoded_rp_id_));
    return query;
  }

  const std::string& metadata_secret() const { return metadata_secret_; }

  const Request& request() const { return request_; }
  Callback& callback() { return callback_; }

 private:
  Request request_;
  // The secret parameter passed to |CredentialMetadata| operations to encrypt
  // or encode credential metadata for storage in the macOS keychain.
  std::string metadata_secret_;
  std::string keychain_access_group_;
  std::string encoded_rp_id_ = "";
  Callback callback_;

  std::unique_ptr<TouchIdContext> touch_id_context_;
};

}  // namespace mac
}  // namespace fido
}  // namespace device

#endif  // DEVICE_FIDO_MAC_OPERATION_BASE_H_
