The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

LWP::UserAgent::msgraph

VERSION

version 0.12

SYNOPSIS

   use LWP::UserAgent::msgraph;

   #The XXXX, YYYY and ZZZZ are from your Azure App Registration

   #Application Permission version
   $ua = LWP::UserAgent::msgraph->new(
      appid => 'XXXX',
      secret => 'YYYY',
      tenant => 'ZZZZ',
      grant_type => 'client_credentials');

   #Delegated authentication version
   $ua = LWP::UserAgent::msgraph->new(   
      appid => 'XXXX',
      secret => 'YYYY',
      tenant => 'ZZZZ',
      grant_type=> 'authorization_code',
      scope => 'openid user.read');
    $ua->auth($code_obtained_from_challenge);

   $joe = $ua->request(GET => '/users/jdoe@some.com');
   $dn = $joe->{displayName};

DESCRIPTION

This module allows the interaction between Perl and the MS Graph API service. Therefore, a MS Graph application can be built using Perl. The application must be correctly registered within Azure with the proper persmissions.

This module has the glue for the needed authentication scheme and the JSON serialization so a conversation can be established with MS Graph. This is just middleware. No higher level object abstraction is provided for the MS Graph object data.

CONSTRUCTOR

   my $ua=LWP::UserAgent->new(%options);

This method constructs a new LWP::UserAgent::msgraph object. key/value pairs must be supplied in order to setup the object properly. Missing mandatory options will result in error

   KEY              MEANING
   -------          -----------------------------------
   appid            Application (client) ID
   secret           shared secret needed for handshake
   tenant           Tenant id
   grant_type       Authorizations scheme (client_credentials,authorization_code)
   scope            List of permissions requested as in 'perm1 perm2 perm3...'
   console          Indicates whether interaction with a user is possible
   redirect_uri     Redirect URI for delegated auth challenge
   local_port       tcp port for mini http server. Defaults to 8081
   sid              Session id. Defaults to a random UUID
   persistent       Whether to keep the session data between runs
   store            Filename for session data. Defaults to a temp file
   base             Base URL for MS Graph calls. Defaults to https://graph.microsoft.com/v1.0

auth

   my $token = $ua->auth;             #For app credentiales            
   my $token = $ua->auth($challenge); #For delegated authentication

This method performs the authentication handshake sequence with the MS Graph platform. The optional parameter is the authorization code obtained from a challenge with the impersonated user. If this is an application non-delegated client, then the $challenge is not needed.

The challenge code is not kept, but the token is. The token is saved for future requests. This method returns the token obtained.

If used in a web application, you should have redirected the user to the authendpoint() location and then capture the resulting code listening for the redirect_uri.

A special tweak is supplied for console applications with delegated authentication. In that case, if the code is missing, an http localhost miniserver is launched so the user can trigger the challenge himself. This behavior is activated via the console constructor option. The http miniserver is destroyed as soon as the authorization code arrives. In this case, the redirect_uri is automatically set. The miniserver listens by default on http://localhost:8081. Please note that MS Graph allows the use of localhost in the redirect_uri and in that case SSL is not enforced. But still the localhost URL must be registered in Azure.

request

   my $object=$ua->request(GET => '/me');
   $ua->request(PATCH => '/me', {officeLocation => $mynewoffice});

The request method makes a call to a MS Graph endpoint url and returns the corresponding response object as a perl structure. An optional perl structure might be supplied as the payload (body) for the request.

The MS Graph has a rich set of API calls for different operations. Check the EXAMPLES section for more tips.

You should call request() only after a successful auth() call. If a refresh_token is issued by MS Graph, then request() will handle the token refresh transparently.

get

   my $me=$ua->get('/me');
   print "Hello $me->{displayName}";

Issues a GET request to the MS Graph endpoint and returns the response as a perl structure.

post

   my $folder=$ua->post('/me/drive/root/children', {name => 'newfolder', folder => {}});

Issues a POST request to the MS Graph endpoint and returns the response as a perl structure. The second parameter is the payload for the POST request, as a perl reference.

code

   print "It worked" if ($ua->code == 201);

A code() method is supplied as a convenient way of getting the last HTTP response code. This mitht be important, since the original HTTP::Respone is lost in the normal operations. You may check HTTP::Status for the meaning and further processing of the codes.

next

   $more=$ua->next();

The next() method will request additional response content after a previous request if a pagination result set happens.

authendpoint

   $location=$ua->authendpoint()

Returns the authentication endpoint as an url string, full with the query part. In a delegated authentication mode, you should point the user to this url via a browser in order to get the proper authorization. This is on offline method, the resulting uri is computed from the constructor options

tokenendpoint

   $location=$ua->tokenendpoint()

Returns the oauth 2.0 token endpoint as an url string. This url is used internally to get the authentication token. This is an offline method.

It's not persistance, it's no cookie. It's Session data

MS Graph implements an OAuth 2.0 authentication scheme. This means that the application asks for an authentication token first and then uses this token for further requests. If you are building a backend application that offers several services, this means that you must keep the token between runs. In a backend for a web application, in theory the token could be kept in the browser local storage, but that's not the approach of LWP::UserAgent::msgraph.

The approach is to store the token under the backend application realm. This is done by the persistent option.

sid

   $sid=$ua->sid();

Returns the session id. This is a random UUID by default. This is used as a key for the session data. If you are building a backend application, you may send this back to the client side in order to keep the session data between runs. Once the sid is created, you can use it in further calls.

   my $sid=<something from the client side>;
   my $ua=LWP::UserAgent->new(sid => $sid, persistent => 1);

By default, UUID based sid are provided. You can use your own sid scheme, but you must ensure that it's unique.

store

   my $sid=<something from the client side>
   my $ua=LWP::UserAgent->new(sid => $sid, persistent => 1, store => "some clever app data location/$sid");

The store option is used to set the filename for the session data. This is a Storable file. If the store option is not set, a temporary file is used. The store option is used in conjunction with the persistent option. The OAuth token is kept in this store. The Application's shared secret is not.

Changes from the default LWP::UserAgent behavior

This class inherits from LWP::UserAgent, but some changes apply. If you are used to LWP::UserAgent standard tweaks and shortcuts, you should read this.

The request() method accepts a perl structure which will be sent as a JSON body to the MS Graph endoint. Instead of an HTTP::Response object, request() will return whatever object is returned by the MS Graph method, as a perl structure. The JSON module is used as a serialization engine. The HTTP::Response object is not kept. The code() method is provided to check the HTTP response code.

request() will use the right Authorization header based on the initial handshake. The get(), post(), patch(), delete(), put(), delete() methods are setup so they call the LWP::UserAgent::msgraph version of request(). That is, they would return a perl structure according to the MS Graph method. In particular, post() and patch() accepts a perl structure as the body. All the binding with the HTTP::Request::Common module has been broken.

The simple_request() method is kept unchanged, but will use the right Bearer token authentication. So, if you need more control over the request, you can use this method. You must add the JSON serialization, though.

Also note that in d LWP, an URL starting with a '/' is considered a root-relative URL. In LWP::UserAgent::msgraph, it's considered relative to the base URL. This is a convenience for MS Graph calls, since MS Graph documentation often uses this convention. simple_request() retains the original root-relative behavior. This is why $ua->get('/me') works.

Requirements

This module requires the following modules:

LWP::UserAgent for all the base HTTP operations.
JSON for the JSON serialization/deserialization.
Storable for the session data storage.
Data::UUID for the session id generation.
HTTP::Server::Simple::CGI and Net::EmptyPort for the http localhost miniserver feature
HTTP::Request::Common is used for the handshake, since it's still multipart/form-data based

TO-DO

This module is a work in progress. The following features are planned:

Certificate based authentication
Allow for a custom storable mechanism, so the session data can be kept in a database or in an encrypted form
Provide useful samples for the most common MS Graph operations