KiCad PCB EDA Suite
Loading...
Searching...
No Matches
oauth_loopback_server.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
21
22#include <wx/tokenzr.h>
23#include <wx/uri.h>
24#include <wx/intl.h>
25
26
27wxDEFINE_EVENT( EVT_OAUTH_LOOPBACK_RESULT, wxCommandEvent );
28
29
30namespace
31{
32wxString queryValue( const wxString& aQuery, const wxString& aKey )
33{
34 wxStringTokenizer queryTokenizer( aQuery, wxS( "&" ) );
35
36 while( queryTokenizer.HasMoreTokens() )
37 {
38 const wxString pair = queryTokenizer.GetNextToken();
39 const int eqPos = pair.Find( '=' );
40
41 if( eqPos == wxNOT_FOUND )
42 continue;
43
44 const wxString name = pair.Left( eqPos );
45 const wxString value = pair.Mid( eqPos + 1 );
46
47 if( name == aKey )
48 return wxURI::Unescape( value );
49 }
50
51 return wxEmptyString;
52}
53} // namespace
54
55
56OAUTH_LOOPBACK_SERVER::OAUTH_LOOPBACK_SERVER( wxEvtHandler* aOwner, const wxString& aCallbackPath,
57 const wxString& aExpectedState ) :
58 m_owner( aOwner ),
59 m_callbackPath( aCallbackPath ),
60 m_expectedState( aExpectedState ),
61 m_port( 0 ),
62 m_done( false )
63{
64 m_timeout.SetOwner( this );
65 Bind( wxEVT_TIMER, &OAUTH_LOOPBACK_SERVER::OnTimeout, this, m_timeout.GetId() );
66}
67
68
70{
71 m_timeout.Stop();
72 Unbind( wxEVT_SOCKET, &OAUTH_LOOPBACK_SERVER::OnSocketEvent, this );
73 Unbind( wxEVT_TIMER, &OAUTH_LOOPBACK_SERVER::OnTimeout, this, m_timeout.GetId() );
74 Shutdown();
75 m_timeout.SetOwner( nullptr );
76}
77
78
80{
81 wxIPV4address addr;
82 addr.Hostname( wxS( "127.0.0.1" ) );
83 addr.Service( 0 );
84
85 std::unique_ptr<wxSocketServer> server =
86 std::make_unique<wxSocketServer>( addr, wxSOCKET_REUSEADDR );
87
88 if( !server->IsOk() )
89 return false;
90
91 server->SetEventHandler( *this );
92 server->SetNotify( wxSOCKET_CONNECTION_FLAG );
93 server->Notify( true );
94
95 wxIPV4address local;
96 server->GetLocal( local );
97 m_port = local.Service();
98
99 Bind( wxEVT_SOCKET, &OAUTH_LOOPBACK_SERVER::OnSocketEvent, this );
100
101 m_server = std::move( server );
102 m_timeout.StartOnce( 120000 );
103 return m_port != 0;
104}
105
106
108{
109 return wxString::Format( wxS( "http://127.0.0.1:%u%s" ), m_port, m_callbackPath );
110}
111
112
113bool OAUTH_LOOPBACK_SERVER::ParseAuthorizationResponse( const wxString& aRequestLine,
114 const wxString& aExpectedPath,
115 const wxString& aExpectedState,
117 wxString& aError )
118{
119 aResponse = OAUTH_AUTHORIZATION_RESPONSE();
120 aError.clear();
121
122 wxStringTokenizer tokenizer( aRequestLine, wxS( " " ) );
123
124 if( !tokenizer.HasMoreTokens() )
125 {
126 aError = _( "Missing HTTP method in callback request." );
127 return false;
128 }
129
130 tokenizer.GetNextToken();
131
132 if( !tokenizer.HasMoreTokens() )
133 {
134 aError = _( "Missing callback URL in request." );
135 return false;
136 }
137
138 const wxString target = tokenizer.GetNextToken();
139 const int queryPos = target.Find( '?' );
140 const wxString path = queryPos == wxNOT_FOUND ? target : target.Left( queryPos );
141 const wxString query = queryPos == wxNOT_FOUND ? wxString() : target.Mid( queryPos + 1 );
142
143 if( path != aExpectedPath )
144 {
145 aError = _( "Unexpected callback path." );
146 return false;
147 }
148
149 wxString state = queryValue( query, wxS( "state" ) );
150
151 if( state != aExpectedState )
152 {
153 aError = _( "OAuth callback state did not match." );
154 return false;
155 }
156
157 aResponse.state = state;
158 aResponse.error = queryValue( query, wxS( "error" ) );
159 aResponse.error_description = queryValue( query, wxS( "error_description" ) );
160
161 if( !aResponse.error.IsEmpty() )
162 {
163 aError = _( "Authorization server returned an OAuth error." );
164 return false;
165 }
166
167 aResponse.code = queryValue( query, wxS( "code" ) );
168
169 if( aResponse.code.IsEmpty() )
170 {
171 aError = _( "Missing authorization code in callback." );
172 return false;
173 }
174
175 return true;
176}
177
178
179void OAUTH_LOOPBACK_SERVER::OnSocketEvent( wxSocketEvent& aEvent )
180{
181 if( !m_server || aEvent.GetSocketEvent() != wxSOCKET_CONNECTION )
182 return;
183
184 std::unique_ptr<wxSocketBase> client( m_server->Accept( false ) );
185
186 if( client )
187 HandleClient( client.get() );
188}
189
190
191void OAUTH_LOOPBACK_SERVER::OnTimeout( wxTimerEvent& aEvent )
192{
193 wxUnusedVar( aEvent );
194 Finish( false );
195}
196
197
198void OAUTH_LOOPBACK_SERVER::HandleClient( wxSocketBase* aClient )
199{
200 if( !aClient )
201 return;
202
203 aClient->SetTimeout( 5 );
204 aClient->SetFlags( wxSOCKET_NONE );
205
206 std::string request;
207 request.reserve( 512 );
208 char buffer[512];
209
210 while( aClient->IsConnected() )
211 {
212 if( !aClient->WaitForRead( 1, 0 ) )
213 break;
214
215 aClient->Read( buffer, sizeof( buffer ) );
216 const size_t count = aClient->LastCount();
217
218 if( count == 0 )
219 break;
220
221 request.append( buffer, count );
222
223 if( request.find( "\r\n\r\n" ) != std::string::npos || request.size() > 4096 )
224 break;
225 }
226
227 const wxString requestWx = wxString::FromUTF8( request.data(), request.size() );
228 const int endOfLine = requestWx.Find( wxS( "\r\n" ) );
229 const wxString requestLine = endOfLine == wxNOT_FOUND ? requestWx : requestWx.Mid( 0, endOfLine );
230
232 wxString error;
233 const bool success = ParseAuthorizationResponse( requestLine, m_callbackPath, m_expectedState,
234 response, error );
235
236 if( success )
237 m_response = response;
238
239 SendHttpResponse( aClient, success );
240 Finish( success );
241}
242
243
244void OAUTH_LOOPBACK_SERVER::SendHttpResponse( wxSocketBase* aClient, bool aSuccess )
245{
246 if( !aClient )
247 return;
248
249 const wxString body = aSuccess
250 ? wxS( "<!DOCTYPE html><html><body><p>Authentication complete.</p></body></html>" )
251 : wxS( "<!DOCTYPE html><html><body><p>Authentication failed.</p></body></html>" );
252 wxScopedCharBuffer bodyUtf8 = body.ToUTF8();
253
254 wxString response;
255 response << wxS( "HTTP/1.1 200 OK\r\n" )
256 << wxS( "Content-Type: text/html; charset=utf-8\r\n" )
257 << wxS( "Cache-Control: no-store\r\n" )
258 << wxS( "Connection: close\r\n" )
259 << wxS( "Content-Length: " ) << bodyUtf8.length() << wxS( "\r\n\r\n" );
260
261 wxScopedCharBuffer header = response.ToUTF8();
262 aClient->Write( header.data(), header.length() );
263 aClient->Write( bodyUtf8.data(), bodyUtf8.length() );
264 aClient->Close();
265}
266
267
268void OAUTH_LOOPBACK_SERVER::Finish( bool aSuccess )
269{
270 if( m_done )
271 return;
272
273 m_done = true;
274 m_timeout.Stop();
275
276 if( m_owner )
277 {
278 wxCommandEvent evt( EVT_OAUTH_LOOPBACK_RESULT );
279 evt.SetInt( aSuccess ? 1 : 0 );
280
281 if( aSuccess && m_response.has_value() )
282 evt.SetString( m_response->code );
283
284 wxQueueEvent( m_owner, evt.Clone() );
285 }
286
287 Shutdown();
288}
289
290
292{
293 if( m_server )
294 {
295 m_server->Notify( false );
296 m_server.reset();
297 }
298}
const char * name
static bool ParseAuthorizationResponse(const wxString &aRequestLine, const wxString &aExpectedPath, const wxString &aExpectedState, OAUTH_AUTHORIZATION_RESPONSE &aResponse, wxString &aError)
void HandleClient(wxSocketBase *aClient)
OAUTH_LOOPBACK_SERVER(wxEvtHandler *aOwner, const wxString &aCallbackPath, const wxString &aExpectedState)
void OnSocketEvent(wxSocketEvent &aEvent)
void OnTimeout(wxTimerEvent &aEvent)
void SendHttpResponse(wxSocketBase *aClient, bool aSuccess)
std::optional< OAUTH_AUTHORIZATION_RESPONSE > m_response
std::unique_ptr< wxSocketServer > m_server
#define _(s)
wxDEFINE_EVENT(EVT_OAUTH_LOOPBACK_RESULT, wxCommandEvent)
std::string path