From 980436e0b407196b325088a9883d0ada2a3306e3 Mon Sep 17 00:00:00 2001 From: David Cook Date: Thu, 22 Jan 2026 05:49:46 +0000 Subject: [PATCH] Bug 38338: Add script to help bootstrap xoauth2 email tokens This change adds a CLI script which can be used to get an initial access token and refresh token for an OAuth2 account to use for XOAUTH2. Test plan: 0. Apply the patch 1. Create an "oauth2.json" file containing a client_id, client_secret, scope, redirect_uri, auth_endpoint, and token_endpoint 2. Run the script using "--conf oauth2.json" and follow the prompts to get an access token and refresh token --- misc/maintenance/xoauth2_email_bootstrap.pl | 131 ++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100755 misc/maintenance/xoauth2_email_bootstrap.pl diff --git a/misc/maintenance/xoauth2_email_bootstrap.pl b/misc/maintenance/xoauth2_email_bootstrap.pl new file mode 100755 index 00000000000..a6f38351402 --- /dev/null +++ b/misc/maintenance/xoauth2_email_bootstrap.pl @@ -0,0 +1,131 @@ +#!/usr/bin/perl + +# +# This file is part of Koha +# +# Koha 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. +# +# Koha 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 Koha; if not, see . +# + +use Modern::Perl; +use LWP::UserAgent; +use URI; +use JSON qw(decode_json); +use Getopt::Long; + +=head1 SYNOPSIS + +perl xoauth2_email_bootstrap.pl --conf oauth2_config.json + +oauth2_config.json must contain the following keys: + client_id, + client_secret, + scope, + redirect_uri, + auth_endpoint, + token_endpoint + +=cut + +my $client_id; +my $client_secret; +my $scope; +my $redirect_uri; +my $auth_endpoint; +my $token_endpoint; + +my $config_filename = ""; +GetOptions( + "config=s" => \$config_filename, +) or die("Error in command line arguments\n"); + +if ($config_filename) { + my $config = parse_config($config_filename); + if ($config) { + $client_id = $config->{client_id}; + $client_secret = $config->{client_secret}; + $scope = $config->{scope}; + $redirect_uri = $config->{redirect_uri}; + $auth_endpoint = $config->{auth_endpoint}; + $token_endpoint = $config->{token_endpoint}; + } +} + +die "No auth_endpoint" unless $auth_endpoint; +die "No token_endpoint" unless $token_endpoint; +die "No client_id" unless $client_id; +die "No client_secret" unless $client_secret; +die "No scope" unless $scope; +die "No redirect_uri" unless $redirect_uri; + +# Do authorizaton code request +my $auth_uri = URI->new($auth_endpoint); +$auth_uri->query_form( + response_type => 'code', + client_id => $client_id, + redirect_uri => $redirect_uri, + scope => $scope, +); + +print "\nGo to this URL in your browser:\n\n"; +print "$auth_uri\n\n"; + +print "After following the workflow in your browser, "; +print "paste the authorization code from the 'code' parameter here:\n> "; +chomp( my $code = ); + +die "No code provided\n" unless $code; + +# Exchange code for tokens +my $ua = LWP::UserAgent->new( + timeout => 20, +); + +my $res = $ua->post( + $token_endpoint, + { + grant_type => 'authorization_code', + code => $code, + client_id => $client_id, + client_secret => $client_secret, + redirect_uri => $redirect_uri, + } +); + +die "Token request failed: " . $res->status_line . "\n" . $res->decoded_content + unless $res->is_success; + +my $token = decode_json( $res->decoded_content ); + +print "\n=== TOKENS ===\n"; +print "Access token:\n$token->{access_token}\n\n"; +print "Refresh token:\n$token->{refresh_token}\n\n"; +print "Expires in:\n$token->{expires_in} seconds\n\n"; + +sub parse_config { + my ($filename) = @_; + if ( $filename && -f $filename ) { + my $opened = open( my $fh, '<', $filename ); + if ($opened) { + local $/; + my $config_json = <$fh>; + if ($config_json) { + my $parsed_config = decode_json($config_json); + if ($parsed_config) { + return $parsed_config; + } + } + } + } + return; +} -- 2.39.5