Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth support to CPDB #37

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ NOTE: The communication protocol between frontends and backends has changed (Job
- [Nilanjana Lodh's Google Summer of Code 2017 Final Report](https://nilanjanalodh.github.io/common-print-dialog-gsoc17/)

- [Gaurav Guleria's Google Summer of Code 2022 Final Report](https://github.com/TinyTrebuchet/gsoc22/)

- [Shivam Sharma's Google Summer of Code 2024 Final Report](https://github.com/shivamsharma2509/GSOC24)
275 changes: 275 additions & 0 deletions src/Auth.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
#define GOA_API_IS_SUBJECT_TO_CHANGE

#include "auth.h"
#include <goa/goa.h>
#include <gtk/gtk.h>
#include <microhttpd.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <curl/curl.h>
#include <json-c/json.h>
#include <sys/stat.h>
#include <unistd.h>

#define PORT 8080
#define AUTHORIZATION_URL "https://github.com/login/oauth/authorize"
#define TOKEN_URL "https://github.com/login/oauth/access_token"
#define TOKEN_FILE "access_token.txt"

/* Global variables */

static gchar *auth_code = NULL;
static GoaClient *client;
GtkWidget *token_label;

/* Function to load configuration from file */

void load_config(char **client_id, char **client_secret) {
FILE *file = fopen("config.cfg", "r");
if (file == NULL) {
g_error("Could not open config.cfg file");
return;
}

char line[256];
while (fgets(line, sizeof(line), file)) {
char *key = strtok(line, "=");
char *value = strtok(NULL, "\n");

if (key != NULL && value != NULL) {
if (strcmp(key, "CLIENT_ID") == 0) {
*client_id = g_strdup(value);
g_print("Loaded CLIENT_ID: %s\n", *client_id);
} else if (strcmp(key, "CLIENT_SECRET") == 0) {
*client_secret = g_strdup(value);
g_print("Loaded CLIENT_SECRET: %s\n", *client_secret);
}
} else {
g_warning("Malformed line in config.cfg: %s", line);
}
}

fclose(file);

if (*client_id == NULL || *client_secret == NULL) {
g_error("Client ID or Client Secret not found in config.cfg");
}
}

/* Function to handle writing data received from curl */

size_t write_callback(void *ptr, size_t size, size_t nmemb, char **data) {
size_t realsize = size * nmemb;
*data = strndup(ptr, realsize); // Duplicate the data received
return realsize;
}

/* Function to handle HTTP requests */

static enum MHD_Result handle_request(void *cls, struct MHD_Connection *connection,
const char *url, const char *method,
const char *version, const char *upload_data,
size_t *upload_data_size, void **con_cls) {
g_print("Received request for URL: %s with method: %s\n", url, method);

if (strcmp(url, "/callback") == 0) {
if (strcmp(method, "GET") == 0) {
const char *code = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "code");

if (code) {
auth_code = g_strdup(code);
g_print("Authorization code received: %s\n", auth_code);

char *client_id = NULL;
char *client_secret = NULL;
load_config(&client_id, &client_secret);

if (client_id == NULL || client_secret == NULL) {
g_error("Client ID or Client Secret is NULL. Check config.cfg file.");
}

/* Exchange authorization code for access token using libcurl */

CURL *curl;
CURLcode res;

curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if (curl) {
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: application/json");

curl_easy_setopt(curl, CURLOPT_URL, TOKEN_URL);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);

gchar *post_data = g_strdup_printf("client_id=%s&client_secret=%s&code=%s",
client_id, client_secret, auth_code);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);

char *response = NULL;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

res = curl_easy_perform(curl);
if (res != CURLE_OK) {
g_warning("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
else {
g_print("Response: %s\n", response);

/* Parse JSON response to extract the access token */

json_object *json_response = json_tokener_parse(response);
json_object *json_access_token;

if (json_object_object_get_ex(json_response, "access_token", &json_access_token)) {
const char *access_token = json_object_get_string(json_access_token);
g_print("Access Token: %s\n", access_token);
gtk_label_set_text(GTK_LABEL(token_label), access_token);

/* Save the access token to a file with restricted permissions */

FILE *token_file = fopen(TOKEN_FILE, "w");
if (token_file) {
fprintf(token_file, "%s\n", access_token);
fclose(token_file);
if (chmod(TOKEN_FILE, S_IRUSR | S_IWUSR) < 0) {
g_warning("Failed to set file permissions for %s", TOKEN_FILE);
}
}
else {
g_warning("Failed to open %s for writing.", TOKEN_FILE);
}
}
else {
g_warning("Failed to extract access token from response.");
gtk_label_set_text(GTK_LABEL(token_label), "Failed to extract access token.");
}
json_object_put(json_response); // free json object
}

g_free(post_data);
curl_easy_cleanup(curl);
if (response) {
free(response);
}
}
curl_global_cleanup();

g_free(client_id);
g_free(client_secret);
}

const char *response_html = "<html><body><h1>Authentication successful! You can close this window. <br /> Happy Printing!!</h1></body></html>";
struct MHD_Response *response_obj = MHD_create_response_from_buffer(strlen(response_html), (void *)response_html, MHD_RESPMEM_PERSISTENT);
int ret = MHD_queue_response(connection, MHD_HTTP_OK, response_obj);
MHD_destroy_response(response_obj);
return ret;
}
}

return MHD_NO;
}

/* Function to start the local server */

void start_local_server() {
struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, &handle_request, NULL, MHD_OPTION_END);
if (daemon == NULL) {
g_error("Failed to start local server.");
}
g_print("Local server started on port %d\n", PORT);
}

/* Function to start the OAuth2 flow */

void start_oauth_flow() {
char *client_id = NULL;
char *client_secret = NULL;
load_config(&client_id, &client_secret);

if (client_id == NULL) {
g_error("Client ID is NULL. Check config.cfg file.");
}

gchar *auth_url = g_strdup_printf(
"%s?client_id=%s&redirect_uri=http://localhost:%d/callback&scope=user",
AUTHORIZATION_URL, client_id, PORT);

g_print("Visit the following URL to authorize the application:\n%s\n", auth_url);

/* Open the URL in the default browser */

gchar *command = g_strdup_printf("xdg-open '%s'", auth_url);
system(command);
g_free(command);

g_free(auth_url);
g_free(client_id);
g_free(client_secret);
}

/* Callback when the GoaClient is ready */

void on_goa_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) {
GError *error = NULL;
client = goa_client_new_finish(res, &error);

if (error) {
g_warning("Failed to initialize GoaClient: %s", error->message);
g_error_free(error);
return;
}

g_print("GoaClient initialized successfully.\n");
}

/* Initialize the GoaClient */

void initialize_goa() {
goa_client_new(NULL, on_goa_ready, NULL);
}

/* Main function */

int main(int argc, char *argv[]) {
gtk_init(&argc, &argv);

/* Create a simple GTK window */

GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "OAuth2 Authentication");
gtk_window_set_default_size(GTK_WINDOW(window), 400, 200);

/* Create a label to display the access token */

token_label = gtk_label_new("No token received yet.");

/* Create an "Authenticate" button */
GtkWidget *auth_button = gtk_button_new_with_label("Authenticate");
g_signal_connect(auth_button, "clicked", G_CALLBACK(start_oauth_flow), NULL);

/* Create a vertical box to hold the label and button */

GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_box_pack_start(GTK_BOX(vbox), token_label, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(vbox), auth_button, TRUE, TRUE, 0);

gtk_container_add(GTK_CONTAINER(window), vbox);

g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

/* Initialize the GoaClient and start the local server */

initialize_goa();
start_local_server();

gtk_widget_show_all(window);
gtk_main();

return 0;
}

2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ cups_SOURCES = \
print_backend_cups.c \
backend_helper.c backend_helper.h \
cups-notifier.c cups-notifier.h
Auth.c auth.h
cups_CPPFLAGS = $(CPDB_CFLAGS)
cups_CPPFLAGS += $(LIBCUPSFILTERS_CFLAGS)
cups_CPPFLAGS += $(GLIB_CFLAGS)
Expand All @@ -32,6 +33,7 @@ cups_LDADD += $(LIBCUPSFILTERS_LIBS)
cups_LDADD += $(GLIB_LIBS)
cups_LDADD += $(GIO_LIBS)
cups_LDADD += $(GIOUNIX_LIBS)
cups_LDADD += -lcurl -ljson-c

# ================================
# Tests ("make test"/"make check")
Expand Down
12 changes: 12 additions & 0 deletions src/auth.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// auth.h
#ifndef AUTH_H
#define AUTH_H

#include <gtk/gtk.h>
#include <goa/goa.h>

// Function prototype for authenticate_user
void authenticate_user(GtkWidget *widget, gpointer data);

#endif // AUTH_H

2 changes: 2 additions & 0 deletions src/config.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CLIENT_ID=Ov23lirB62bclUpviNM1
CLIENT_SECRET=c409cb708c71b58f854da44cbd5c1fff56e6a6d3
48 changes: 48 additions & 0 deletions src/print_backend_cups.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#define GOA_API_IS_SUBJECT_TO_CHANGE

#include "auth.h"
#include <goa/goa.h>
#include <gtk/gtk.h>

#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
Expand Down Expand Up @@ -496,6 +502,25 @@ static gboolean on_handle_ping(PrintBackend *interface,
return TRUE;
}

static gboolean send_print_job_with_token(PrinterCUPS *printer,
const gchar *printer_id,
int num_settings,
GVariant *settings,
const gchar *title,
const char *access_token,
char *jobid_out,
char *socket_out)
{
if(!access_token){
g_warning("No access token available. Authentication is required.");
return FALSE;
}

print_socket(printer, num_settings, settings, jobid_out, socket_out, title);
g_message("Print job sent successfully.");

return TRUE;
}
static gboolean on_handle_print_socket(PrintBackend *interface,
GDBusMethodInvocation *invocation,
const gchar *printer_id,
Expand All @@ -506,18 +531,41 @@ static gboolean on_handle_print_socket(PrintBackend *interface,
{
const char *dialog_name = g_dbus_method_invocation_get_sender(invocation);
PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_id);

if(!p){
g_dbus_method_invocation_return_error(invocation, G_IO_ERROR, G_IO_ERROR_FAILED, "Printer not found");
return FALSE;
}

/* Call OAuth function to retrieve access token */
char *access_token = get_access_token();
if(!access_token){
g_dbus_method_invocation_return_error(invocation, G_IO_ERROR, G_IO_ERROR_FAILED, "Authentication required");
return FALSE;
}

// Call the renamed function
char jobid[32];
char socket[256];
print_socket(p, num_settings, settings, jobid, socket, title);

gboolean success = send_print_job_with_token(p, printer_id, num_settings, settings, title, access_token, jobid, socket);
if(!success){
g_dbus_method_invocation_return_error(invocation, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to send print job.");
g_free(access_token);
return FALSE;
}

// Complete the D-Bus method call with the result
print_backend_complete_print_socket(interface, invocation, jobid, socket);

//Free access token if dynamically allocated
g_free(access_token);

return TRUE;
}


static gboolean on_handle_get_all_options(PrintBackend *interface,
GDBusMethodInvocation *invocation,
const gchar *printer_name,
Expand Down