299 lines
8.1 KiB
C++
299 lines
8.1 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <oauth/oauth_loopback_server.h>
|
|
|
|
#include <wx/tokenzr.h>
|
|
#include <wx/uri.h>
|
|
#include <wx/intl.h>
|
|
|
|
|
|
wxDEFINE_EVENT( EVT_OAUTH_LOOPBACK_RESULT, wxCommandEvent );
|
|
|
|
|
|
namespace
|
|
{
|
|
wxString queryValue( const wxString& aQuery, const wxString& aKey )
|
|
{
|
|
wxStringTokenizer queryTokenizer( aQuery, wxS( "&" ) );
|
|
|
|
while( queryTokenizer.HasMoreTokens() )
|
|
{
|
|
const wxString pair = queryTokenizer.GetNextToken();
|
|
const int eqPos = pair.Find( '=' );
|
|
|
|
if( eqPos == wxNOT_FOUND )
|
|
continue;
|
|
|
|
const wxString name = pair.Left( eqPos );
|
|
const wxString value = pair.Mid( eqPos + 1 );
|
|
|
|
if( name == aKey )
|
|
return wxURI::Unescape( value );
|
|
}
|
|
|
|
return wxEmptyString;
|
|
}
|
|
} // namespace
|
|
|
|
|
|
OAUTH_LOOPBACK_SERVER::OAUTH_LOOPBACK_SERVER( wxEvtHandler* aOwner, const wxString& aCallbackPath,
|
|
const wxString& aExpectedState ) :
|
|
m_owner( aOwner ),
|
|
m_callbackPath( aCallbackPath ),
|
|
m_expectedState( aExpectedState ),
|
|
m_port( 0 ),
|
|
m_done( false )
|
|
{
|
|
m_timeout.SetOwner( this );
|
|
Bind( wxEVT_TIMER, &OAUTH_LOOPBACK_SERVER::OnTimeout, this, m_timeout.GetId() );
|
|
}
|
|
|
|
|
|
OAUTH_LOOPBACK_SERVER::~OAUTH_LOOPBACK_SERVER()
|
|
{
|
|
m_timeout.Stop();
|
|
Unbind( wxEVT_SOCKET, &OAUTH_LOOPBACK_SERVER::OnSocketEvent, this );
|
|
Unbind( wxEVT_TIMER, &OAUTH_LOOPBACK_SERVER::OnTimeout, this, m_timeout.GetId() );
|
|
Shutdown();
|
|
m_timeout.SetOwner( nullptr );
|
|
}
|
|
|
|
|
|
bool OAUTH_LOOPBACK_SERVER::Start()
|
|
{
|
|
wxIPV4address addr;
|
|
addr.Hostname( wxS( "127.0.0.1" ) );
|
|
addr.Service( 0 );
|
|
|
|
std::unique_ptr<wxSocketServer> server =
|
|
std::make_unique<wxSocketServer>( addr, wxSOCKET_REUSEADDR );
|
|
|
|
if( !server->IsOk() )
|
|
return false;
|
|
|
|
server->SetEventHandler( *this );
|
|
server->SetNotify( wxSOCKET_CONNECTION_FLAG );
|
|
server->Notify( true );
|
|
|
|
wxIPV4address local;
|
|
server->GetLocal( local );
|
|
m_port = local.Service();
|
|
|
|
Bind( wxEVT_SOCKET, &OAUTH_LOOPBACK_SERVER::OnSocketEvent, this );
|
|
|
|
m_server = std::move( server );
|
|
m_timeout.StartOnce( 120000 );
|
|
return m_port != 0;
|
|
}
|
|
|
|
|
|
wxString OAUTH_LOOPBACK_SERVER::GetRedirectUri() const
|
|
{
|
|
return wxString::Format( wxS( "http://127.0.0.1:%u%s" ), m_port, m_callbackPath );
|
|
}
|
|
|
|
|
|
bool OAUTH_LOOPBACK_SERVER::ParseAuthorizationResponse( const wxString& aRequestLine,
|
|
const wxString& aExpectedPath,
|
|
const wxString& aExpectedState,
|
|
OAUTH_AUTHORIZATION_RESPONSE& aResponse,
|
|
wxString& aError )
|
|
{
|
|
aResponse = OAUTH_AUTHORIZATION_RESPONSE();
|
|
aError.clear();
|
|
|
|
wxStringTokenizer tokenizer( aRequestLine, wxS( " " ) );
|
|
|
|
if( !tokenizer.HasMoreTokens() )
|
|
{
|
|
aError = _( "Missing HTTP method in callback request." );
|
|
return false;
|
|
}
|
|
|
|
tokenizer.GetNextToken();
|
|
|
|
if( !tokenizer.HasMoreTokens() )
|
|
{
|
|
aError = _( "Missing callback URL in request." );
|
|
return false;
|
|
}
|
|
|
|
const wxString target = tokenizer.GetNextToken();
|
|
const int queryPos = target.Find( '?' );
|
|
const wxString path = queryPos == wxNOT_FOUND ? target : target.Left( queryPos );
|
|
const wxString query = queryPos == wxNOT_FOUND ? wxString() : target.Mid( queryPos + 1 );
|
|
|
|
if( path != aExpectedPath )
|
|
{
|
|
aError = _( "Unexpected callback path." );
|
|
return false;
|
|
}
|
|
|
|
wxString state = queryValue( query, wxS( "state" ) );
|
|
|
|
if( state != aExpectedState )
|
|
{
|
|
aError = _( "OAuth callback state did not match." );
|
|
return false;
|
|
}
|
|
|
|
aResponse.state = state;
|
|
aResponse.error = queryValue( query, wxS( "error" ) );
|
|
aResponse.error_description = queryValue( query, wxS( "error_description" ) );
|
|
|
|
if( !aResponse.error.IsEmpty() )
|
|
{
|
|
aError = _( "Authorization server returned an OAuth error." );
|
|
return false;
|
|
}
|
|
|
|
aResponse.code = queryValue( query, wxS( "code" ) );
|
|
|
|
if( aResponse.code.IsEmpty() )
|
|
{
|
|
aError = _( "Missing authorization code in callback." );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void OAUTH_LOOPBACK_SERVER::OnSocketEvent( wxSocketEvent& aEvent )
|
|
{
|
|
if( !m_server || aEvent.GetSocketEvent() != wxSOCKET_CONNECTION )
|
|
return;
|
|
|
|
std::unique_ptr<wxSocketBase> client( m_server->Accept( false ) );
|
|
|
|
if( client )
|
|
HandleClient( client.get() );
|
|
}
|
|
|
|
|
|
void OAUTH_LOOPBACK_SERVER::OnTimeout( wxTimerEvent& aEvent )
|
|
{
|
|
wxUnusedVar( aEvent );
|
|
Finish( false );
|
|
}
|
|
|
|
|
|
void OAUTH_LOOPBACK_SERVER::HandleClient( wxSocketBase* aClient )
|
|
{
|
|
if( !aClient )
|
|
return;
|
|
|
|
aClient->SetTimeout( 5 );
|
|
aClient->SetFlags( wxSOCKET_NONE );
|
|
|
|
std::string request;
|
|
request.reserve( 512 );
|
|
char buffer[512];
|
|
|
|
while( aClient->IsConnected() )
|
|
{
|
|
if( !aClient->WaitForRead( 1, 0 ) )
|
|
break;
|
|
|
|
aClient->Read( buffer, sizeof( buffer ) );
|
|
const size_t count = aClient->LastCount();
|
|
|
|
if( count == 0 )
|
|
break;
|
|
|
|
request.append( buffer, count );
|
|
|
|
if( request.find( "\r\n\r\n" ) != std::string::npos || request.size() > 4096 )
|
|
break;
|
|
}
|
|
|
|
const wxString requestWx = wxString::FromUTF8( request.data(), request.size() );
|
|
const int endOfLine = requestWx.Find( wxS( "\r\n" ) );
|
|
const wxString requestLine = endOfLine == wxNOT_FOUND ? requestWx : requestWx.Mid( 0, endOfLine );
|
|
|
|
OAUTH_AUTHORIZATION_RESPONSE response;
|
|
wxString error;
|
|
const bool success = ParseAuthorizationResponse( requestLine, m_callbackPath, m_expectedState,
|
|
response, error );
|
|
|
|
if( success )
|
|
m_response = response;
|
|
|
|
SendHttpResponse( aClient, success );
|
|
Finish( success );
|
|
}
|
|
|
|
|
|
void OAUTH_LOOPBACK_SERVER::SendHttpResponse( wxSocketBase* aClient, bool aSuccess )
|
|
{
|
|
if( !aClient )
|
|
return;
|
|
|
|
const wxString body = aSuccess
|
|
? wxS( "<!DOCTYPE html><html><body><p>Authentication complete.</p></body></html>" )
|
|
: wxS( "<!DOCTYPE html><html><body><p>Authentication failed.</p></body></html>" );
|
|
wxScopedCharBuffer bodyUtf8 = body.ToUTF8();
|
|
|
|
wxString response;
|
|
response << wxS( "HTTP/1.1 200 OK\r\n" )
|
|
<< wxS( "Content-Type: text/html; charset=utf-8\r\n" )
|
|
<< wxS( "Cache-Control: no-store\r\n" )
|
|
<< wxS( "Connection: close\r\n" )
|
|
<< wxS( "Content-Length: " ) << bodyUtf8.length() << wxS( "\r\n\r\n" );
|
|
|
|
wxScopedCharBuffer header = response.ToUTF8();
|
|
aClient->Write( header.data(), header.length() );
|
|
aClient->Write( bodyUtf8.data(), bodyUtf8.length() );
|
|
aClient->Close();
|
|
}
|
|
|
|
|
|
void OAUTH_LOOPBACK_SERVER::Finish( bool aSuccess )
|
|
{
|
|
if( m_done )
|
|
return;
|
|
|
|
m_done = true;
|
|
m_timeout.Stop();
|
|
|
|
if( m_owner )
|
|
{
|
|
wxCommandEvent evt( EVT_OAUTH_LOOPBACK_RESULT );
|
|
evt.SetInt( aSuccess ? 1 : 0 );
|
|
|
|
if( aSuccess && m_response.has_value() )
|
|
evt.SetString( m_response->code );
|
|
|
|
wxQueueEvent( m_owner, evt.Clone() );
|
|
}
|
|
|
|
Shutdown();
|
|
}
|
|
|
|
|
|
void OAUTH_LOOPBACK_SERVER::Shutdown()
|
|
{
|
|
if( m_server )
|
|
{
|
|
m_server->Notify( false );
|
|
m_server.reset();
|
|
}
|
|
}
|