1
#pragma once
2

            
3
#include <map>
4
#include <string>
5

            
6
#include "envoy/common/pure.h"
7
#include "envoy/config/core/v3/http_uri.pb.h"
8
#include "envoy/http/async_client.h"
9
#include "envoy/http/message.h"
10
#include "envoy/upstream/cluster_manager.h"
11

            
12
#include "source/common/http/headers.h"
13
#include "source/common/http/message_impl.h"
14
#include "source/common/http/utility.h"
15
#include "source/extensions/http/injected_credentials/oauth2/oauth.h"
16

            
17
namespace Envoy {
18
namespace Extensions {
19
namespace Http {
20
namespace InjectedCredentials {
21
namespace OAuth2 {
22

            
23
/**
24
 * An OAuth client abstracts away everything regarding how to communicate with
25
 * the OAuth server. It is responsible for sending the request to the OAuth server
26
 * and handling the response.
27
 */
28
class OAuth2Client : public Envoy::Http::AsyncClient::Callbacks {
29
public:
30
  enum class GetTokenResult {
31
    NotDispatchedClusterNotFound,
32
    NotDispatchedAlreadyInFlight,
33
    DispatchedRequest,
34
  };
35
  virtual GetTokenResult
36
  asyncGetAccessToken(const std::string& client_id, const std::string& secret,
37
                      const std::string& scopes,
38
                      const std::map<std::string, std::string>& endpoint_params) PURE;
39
  virtual void setCallbacks(FilterCallbacks& callbacks) PURE;
40

            
41
  // Http::AsyncClient::Callbacks
42
  void onSuccess(const Envoy::Http::AsyncClient::Request&,
43
                 Envoy::Http::ResponseMessagePtr&& m) override PURE;
44
  void onFailure(const Envoy::Http::AsyncClient::Request&,
45
                 Envoy::Http::AsyncClient::FailureReason f) override PURE;
46
};
47

            
48
class OAuth2ClientImpl : public OAuth2Client, Logger::Loggable<Logger::Id::credential_injector> {
49
public:
50
  OAuth2ClientImpl(Upstream::ClusterManager& cm, const envoy::config::core::v3::HttpUri& uri)
51
25
      : cm_(cm), uri_(uri) {}
52

            
53
25
  ~OAuth2ClientImpl() override {
54
25
    if (in_flight_request_ != nullptr) {
55
      in_flight_request_->cancel();
56
    }
57
25
  }
58

            
59
  // OAuth2Client
60
  /**
61
   * Request the access token from the OAuth server. Calls the `onSuccess` on `onFailure` callbacks.
62
   */
63
  GetTokenResult
64
  asyncGetAccessToken(const std::string& client_id, const std::string& secret,
65
                      const std::string& scopes,
66
                      const std::map<std::string, std::string>& endpoint_params) override;
67

            
68
25
  void setCallbacks(FilterCallbacks& callbacks) override { parent_ = &callbacks; }
69

            
70
  // AsyncClient::Callbacks
71
  void onSuccess(const Envoy::Http::AsyncClient::Request&,
72
                 Envoy::Http::ResponseMessagePtr&& m) override;
73
  void onFailure(const Envoy::Http::AsyncClient::Request&,
74
                 Envoy::Http::AsyncClient::FailureReason f) override;
75
  void onBeforeFinalizeUpstreamSpan(Envoy::Tracing::Span&,
76
28
                                    const Envoy::Http::ResponseHeaderMap*) override {}
77

            
78
private:
79
  FilterCallbacks* parent_{nullptr};
80
  Upstream::ClusterManager& cm_;
81
  const envoy::config::core::v3::HttpUri uri_;
82
  // Tracks any outstanding in-flight requests, allowing us to cancel the request
83
  // if the filter ends before the request completes.
84
  Envoy::Http::AsyncClient::Request* in_flight_request_{nullptr};
85
  /**
86
   * Begins execution of an asynchronous request.
87
   *
88
   * @param request the HTTP request to be executed.
89
   */
90
  GetTokenResult dispatchRequest(Envoy::Http::RequestMessagePtr&& request);
91
32
  Envoy::Http::RequestMessagePtr createPostRequest() {
92
32
    auto request = Envoy::Http::Utility::prepareHeaders(uri_);
93
32
    request->headers().setReferenceMethod(Envoy::Http::Headers::get().MethodValues.Post);
94
32
    request->headers().setReferenceContentType(
95
32
        Envoy::Http::Headers::get().ContentTypeValues.FormUrlEncoded);
96
32
    request->headers().setReferenceUserAgent(Envoy::Http::Headers::get().UserAgentValues.GoBrowser);
97
    // Use the Accept header to ensure the Access Token Response is returned as JSON.
98
    // Some authorization servers return other encodings (e.g. FormUrlEncoded) in the absence of the
99
    // Accept header. RFC 6749 Section 5.1 defines the media type to be JSON, so this is safe.
100
32
    request->headers().setReference(Envoy::Http::CustomHeaders::get().Accept,
101
32
                                    Envoy::Http::Headers::get().ContentTypeValues.Json);
102
32
    return request;
103
32
  }
104
};
105

            
106
} // namespace OAuth2
107
} // namespace InjectedCredentials
108
} // namespace Http
109
} // namespace Extensions
110
} // namespace Envoy