You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

321 lines
8.4 KiB
C

#include "oauth.h"
#include "http/server/request.h"
#include "http/server/escape.h"
#include "model/client_app.h"
#include "model/account.h"
#include "model/owner.h"
#include "form.h"
#include "json/json.h"
#include "json/layout.h"
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
static bool handle_oauth_authorize( struct http_request* req )
{
char* client_id = NULL;
char* redirect_uri = NULL;
char* response_type = NULL;
bool result = true;
struct {
bool read;
bool write;
bool follow;
} scopes = { false, false, false };
// Parse query parameters
{
const char* key;
while(( key = http_request_route_query_key(req) )) {
if( 0 == strcmp(key,"client_id") ) {
client_id = strdup(http_request_route_query_value(req));
} else if( 0 == strcmp(key,"redirect_uri") ) {
redirect_uri = strdup(http_request_route_query_value(req));
// Validate redirection uri
bool valid = false;
if( 0 == strncmp("oauth2redirect://",redirect_uri,17) ) {
valid = true;
}
if( !valid ) {
result = false; goto cleanup;
}
} else if( 0 == strcmp(key,"response_type") ) {
if( 0 != strcmp(http_request_route_query_value(req),"code") ) {
result = false; goto cleanup;
}
} else if( 0 == strcmp(key,"scope") ) {
char* scope = strdup(http_request_route_query_value(req));
char* resume = scope;
char* item;
while(( item = strtok_r( NULL, " ", &resume ) )) {
if( 0 == strcmp(item,"read") ) {
scopes.read = true;
} else if( 0 == strcmp(item,"write") ) {
scopes.write = true;
} else if( 0 == strcmp(item,"follow") ) {
scopes.follow = true;
} else {
free(scope);
result = false; goto cleanup;
}
}
free(scope);
} else {
printf( "key=%s\n", key );
printf( "value=%s\n\n", http_request_route_query_value(req) );
}
}
}
struct client_app* app = client_app_from_id( client_id );
if( !app) {
result =false; goto cleanup;
}
// Store the redirect URI for later use
free(app->redirect_uri);
app->redirect_uri = redirect_uri;
client_app_save( app );
http_request_send_headers( req, 200, "text/html", true );
FILE* f = http_request_get_response_body( req );
#define RENDER
#include "view/authorize.html.inc"
#undef RENDER
result = true;
cleanup:
free(client_id);
free(redirect_uri);
free(response_type);
return result;
}
static bool handle_oauth_do_authorize( struct http_request* req )
{
char* password = NULL;
char* state = NULL;
struct owner* o = owner_new();
struct client_app* app = NULL;
FILE* body = http_request_get_request_data(req);
struct form_parser* fp = form_pull_parser_new(body);
if( !fp ) {
goto invalid_request;
}
char* key;
while(( key = form_pull_parser_read_key( fp ) )) {
if( 0 == strcmp(key,"password") ) {
password = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"state") ) {
state = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"id") ) {
const char* client_id = strdup(form_pull_parser_read_value(fp));
app = client_app_from_id( client_id );
if( !app ) { goto invalid_request; }
}
}
form_pull_parser_release(fp);
if( !app ) { goto invalid_request; }
if( !password ) { goto invalid_request; }
if( !owner_check_password( o, password ) ) {
owner_free(o);
free(password);
goto access_denied;
}
client_app_gen_auth_code( app );
char location[512];
char workspace[1024];
const char* fmt = ( state ? "%s?code=%s&state=%s" : "%s?code=%s" );
snprintf( location, 512, fmt, http_escape( app->redirect_uri, workspace, 1024, ":/" ), app->auth_code, state );
printf( "redirecting to %s\n", location );
http_request_begin_send_headers( req, 302, false );
http_request_send_header( req, "Location", location );
http_request_end_send_headers( req, false );
free(password);
return false;
invalid_request:
printf( "Invalid request\n" );
http_request_send_headers( req, 400, "text/plain", false );
fprintf( http_request_get_response_body(req), "invalid_request" );
return true;
access_denied:
printf( "Access denied\n" );
http_request_send_headers( req, 400, "text/plain", false );
fprintf( http_request_get_response_body(req), "access_denied" );
return true;
}
static bool handle_oauth_get_token( struct http_request* req )
{
bool result = true;
struct account* owner_account = NULL;
struct owner* owner = NULL;
struct client_app* app = NULL;
struct data_t
{
char* code;
char* redirect_uri;
char* client_secret;
char* client_id;
char* grant_type;
char* username;
char* password;
} data;
memset(&data,0,sizeof(data));
FILE* post_data = http_request_get_request_data(req);
const char* content_type = http_request_get_header( req, "Content-Type" );
if( 0 == strcasecmp(content_type,"application/json") ) {
#define OBJ_TYPE struct data_t
static struct json_object_field layout[] = {
JSON_FIELD_STRING( code, false ),
JSON_FIELD_STRING( redirect_uri, false ),
JSON_FIELD_STRING( grant_type, false ),
JSON_FIELD_STRING( client_secret, false ),
JSON_FIELD_STRING( client_id, false ),
JSON_FIELD_STRING( username, false ),
JSON_FIELD_STRING( password, false ),
JSON_FIELD_END,
};
#undef OBJ_TYPE
if( !json_read_object_layout_from_FILE( post_data, layout, &data ) ) {
printf( "A" );
goto invalid_request;
}
} else {
struct form_parser* fp = form_pull_parser_new(post_data);
if( !fp ) {
goto invalid_request;
}
char* key;
// TODO: extend form parser to use a data layout
while(( key = form_pull_parser_read_key( fp ) )) {
if( 0 == strcmp(key,"grant_type") ) {
data.grant_type = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"code") ) {
data.code = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"client_id") ) {
data.client_id = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"client_secret") ) {
data.client_secret = strdup(form_pull_parser_read_value(fp));
} else if( 0 == strcmp(key,"redirect_uri") ) {
data.redirect_uri = strdup(form_pull_parser_read_value(fp));
} else {
printf( "%s=", key );
printf( "%s\n", form_pull_parser_read_value(fp) );
}
}
form_pull_parser_release(fp);
}
printf( "2" );
if( !data.redirect_uri ) { goto invalid_request; }
printf( "3" );
if( !data.client_id ) { goto invalid_request; }
printf( "4" );
app = client_app_from_id( data.client_id );
if( !app ) { goto invalid_request; }
printf( "5" );
if( !app->redirect_uri ) { goto invalid_request; }
if( 0 != strcmp( app->redirect_uri, data.redirect_uri ) ) { goto invalid_request; }
printf( "6" );
if( 0 != strcmp( app->client.secret, data.client_secret ) ) { goto access_denied; }
if( 0 == strcmp("authorization_code",data.grant_type) ) {
if( !data.code ) { goto invalid_request; }
if( 0 != strcmp( app->auth_code, data.code ) ) { goto access_denied; }
} else if( 0 == strcmp("password", data.grant_type ) ) {
owner_account = account_from_id(0);
owner = owner_new();
if( 0 != strcmp( owner_account->handle, data.username ) ) { goto access_denied; }
if( !owner_check_password( owner, data.password ) ) { goto access_denied; }
} else {
printf( "7" );
goto invalid_request;
}
// TODO: check code has not expired
client_app_generate_access_token( app );
http_request_begin_send_headers( req, 200, false );
http_request_send_header( req, "Content-Type", "application/json" );
http_request_send_header( req, "Cache-Control", "no-store" );
http_request_end_send_headers( req, false );
FILE* f = http_request_get_response_body( req );
#define RENDER
#include "view/token_grant.json.inc"
#undef RENDER
cleanup:
client_app_free(app);
free(data.code);
free(data.redirect_uri);
free(data.client_secret);
free(data.client_id);
free(data.grant_type);
free(data.password);
free(data.username);
owner_free(owner);
account_free(owner_account);
return result;
access_denied:
printf( "Access denied\n" );
result = false;
goto cleanup;
invalid_request:
printf( "Invalid request\n" );
result = false;
goto cleanup;
}
bool route_oauth( struct http_request* req )
{
if( http_request_route( req, "/authorize" ) ) {
if( http_request_route_method(req,"POST") ) {
return handle_oauth_do_authorize(req);
} else if( http_request_route( req, "?" ) ) {
return handle_oauth_authorize(req);
}
} else if( http_request_route( req, "/token" ) ) {
if( http_request_route_method(req,"POST") ) {
return handle_oauth_get_token(req);
}
}
return false;
}