1 /*
2  * Copyright (C) 2021 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 #include "Process.h"
7 
8 #include "Service.h"
9 
10 #include "ProcessImpl.h"
11 #include "ServiceImpl.h"
12 
13 #include "Wt/PopupWindow.h"
14 #include "Wt/WApplication.h"
15 #include "Wt/WEnvironment.h"
16 #include "Wt/WLogger.h"
17 #include "Wt/WResource.h"
18 
19 #include "Wt/Auth/Identity.h"
20 
21 #include "Wt/Http/Client.h"
22 #include "Wt/Http/Request.h"
23 #include "Wt/Http/Response.h"
24 
25 #include "web/WebUtils.h"
26 
27 namespace Wt {
28 
29 LOGGER("Auth.Saml.Process");
30 
31 }
32 
33 namespace Wt {
34 namespace Auth {
35 namespace Saml {
36 
37 class Process::AuthnRequestResource final : public WResource {
38 public:
39   explicit AuthnRequestResource(Process &process);
40 
41   ~AuthnRequestResource() final;
42 
43 protected:
44   void handleRequest(const Http::Request &request, Http::Response &response) final;
45 
46 private:
47   Process &process_;
48 };
49 
50 class Process::PrivateAcsResource final : public WResource {
51 public:
52   explicit PrivateAcsResource(Process &process);
53 
54   ~PrivateAcsResource() final;
55 
56 protected:
57   void handleRequest(const Http::Request &request, Http::Response &response) final;
58 
59 private:
60   Process &process_;
61 };
62 
AuthnRequestResource(Process & process)63 Process::AuthnRequestResource::AuthnRequestResource(Process &process)
64   : process_(process)
65 {
66   setTakesUpdateLock(true);
67 }
68 
~AuthnRequestResource()69 Process::AuthnRequestResource::~AuthnRequestResource()
70 {
71   beingDeleted();
72 }
73 
handleRequest(const Http::Request &,Http::Response & response)74 void Process::AuthnRequestResource::handleRequest(const Http::Request &,
75                                                   Http::Response &response)
76 {
77   bool success = process_.createAuthnRequest(response);
78   if (!success) {
79     response.setStatus(500);
80     response.setMimeType("text/html");
81     response.out() << "<html><body>"
82                    << "<h1>SAML Authentication error</h1>"
83                    << "</body></html>";
84   }
85 }
86 
PrivateAcsResource(Process & process)87 Process::PrivateAcsResource::PrivateAcsResource(Process &process)
88   : process_(process)
89 {
90   setTakesUpdateLock(true);
91 }
92 
~PrivateAcsResource()93 Process::PrivateAcsResource::~PrivateAcsResource()
94 {
95   beingDeleted();
96 }
97 
handleRequest(const Http::Request & request,Http::Response & response)98 void Process::PrivateAcsResource::handleRequest(const Http::Request &request,
99                                                 Http::Response &response)
100 {
101   if (process_.handleResponse(request)) {
102 #ifndef WT_TARGET_JAVA
103     std::ostream &o = response.out();
104 #else // WT_TARGET_JAVA
105     std::ostream o(response.out());
106 #endif // WT_TARGET_JAVA
107     WApplication *app = WApplication::instance();
108     const bool usePopup = app->environment().ajax() && process_.service_.popupEnabled();
109 
110     if (!usePopup) {
111 #ifndef WT_TARGET_JAVA
112       WApplication::UpdateLock lock(app);
113 #endif
114       process_.doneCallbackConnection_ =
115         app->unsuspended().connect(&process_, &Process::onSamlDone);
116 
117       std::string redirectTo = app->makeAbsoluteUrl(app->url(process_.startInternalPath_));
118       response.setStatus(303);
119       response.addHeader("Location", redirectTo);
120     } else {
121       std::string appJs = app->javaScriptClass();
122       o <<
123         "<!DOCTYPE html>"
124         "<html lang=\"en\" dir=\"ltr\">\n"
125         "<head><title></title>\n"
126         "<script type=\"text/javascript\">\n"
127         "function load() { "
128         """if (window.opener." << appJs << ") {"
129         ""  "var " << appJs << "= window.opener." << appJs << ";"
130 #ifndef WT_TARGET_JAVA
131         << process_.redirected_.createCall({}) << ";"
132 #else // WT_TARGET_JAVA
133         <<  process_.redirected_.createCall() << ";"
134 #endif // WT_TARGET_JAVA
135         ""  "window.close();"
136         "}\n"
137         "}\n"
138         "</script></head>"
139         "<body onload=\"load();\"></body></html>";
140     }
141   } else {
142     response.setStatus(500);
143     response.setMimeType("text/html");
144     response.out() << "<html><body>"
145                    << "<h1>SAML Authentication error</h1>"
146                    << "</body></html>";
147   }
148 }
149 
Process(const Service & service)150 Process::Process(const Service &service)
151   : service_(service),
152     redirected_(this, "redirected")
153 {
154   impl_ = std::make_unique<ProcessImpl>(*this);
155   authnRequestResource_ = std::make_unique<AuthnRequestResource>(*this);
156   privateAcsResource_ = std::make_unique<PrivateAcsResource>(*this);
157 
158   WApplication *app = WApplication::instance();
159   PopupWindow::loadJavaScript(app);
160 
161   std::string url = app->makeAbsoluteUrl(authnRequestResource_->url());
162 
163   redirected_.connect(this, &Process::onSamlDone);
164 
165 #ifndef WT_TARGET_JAVA
166   if (service_.popupEnabled()) {
167     WStringStream js;
168     js << WT_CLASS << ".PopupWindow(" WT_CLASS
169        << "," << WWebWidget::jsStringLiteral(url)
170        << ", " << service.popupWidth()
171        << ", " << service.popupHeight() << ");";
172 
173     implementJavaScript(&Process::startAuthenticate, js.str());
174   }
175 #endif
176 }
177 
~Process()178 Process::~Process()
179 { }
180 
startAuthenticate()181 void Process::startAuthenticate()
182 {
183   WApplication *app = WApplication::instance();
184   if (app->environment().javaScript() && service_.popupEnabled()) {
185     return;
186   }
187 
188   app->suspend(service_.redirectTimeout_);
189 
190   startInternalPath_ = app->internalPath();
191   app->redirect(authnRequestResource_->url());
192 }
193 
194 #ifdef WT_TARGET_JAVA
connectStartAuthenticate(EventSignalBase & s)195 void Process::connectStartAuthenticate(EventSignalBase &s)
196 {
197   WApplication *app = WApplication::instance();
198   if (app->environment().javaScript()) {
199     std::string url = app->makeAbsoluteUrl(authnRequestResource_->url());
200     WStringStream js;
201     js << "function(object, event) {"
202        << WT_CLASS ".PopupWindow(" WT_CLASS
203        << "," << WWebWidget::jsStringLiteral(url)
204        << ", " << service_.popupWidth()
205        << ", " << service_.popupHeight() << ");"
206        << "}";
207 
208     s.connect(js.str());
209   }
210 
211   s.connect(this, &Process::startAuthenticate);
212 }
213 #endif
214 
createAuthnRequest(Http::Response & response)215 bool Process::createAuthnRequest(Http::Response &response)
216 {
217   return impl_->createAuthnRequest(response);
218 }
219 
handleResponse(const Http::Request & request)220 bool Process::handleResponse(const Http::Request &request)
221 {
222   return impl_->handleResponse(request);
223 }
224 
onSamlDone()225 void Process::onSamlDone()
226 {
227   bool success = error_.empty();
228 
229   authenticated().emit(success ? service_.assertionToIdentity(assertion_) : Identity());
230 
231   if (doneCallbackConnection_.isConnected())
232     doneCallbackConnection_.disconnect();
233 }
234 
privateAcsResourceUrl()235 std::string Process::privateAcsResourceUrl() const
236 {
237   return privateAcsResource_->url();
238 }
239 
240 }
241 }
242 }
243