BookmarkSubscribeRSS Feed
Rutujak
Calcite | Level 5

/* ----------------------------------------------------
Utility macros to manage and refresh the access token
for using Microsoft OneDrive APIs from SAS,
using PROC HTTP.

Authors: Joseph Henry, SAS
Chris Hemedinger, SAS
Copyright 2018, SAS Institute Inc.
-------------------------------------------------------*/

/*
Utility macro to process the JSON token
file that was created at authorization time.
This will fetch the access token, refresh token,
and expiration datetime for the token so we know
if we need to refresh it.
*/
%macro process_token_file(file);
libname oauth json fileref=&file.;

data _null_;
set oauth.root;
call symputx('access_token', access_token,'G');
call symputx('refresh_token', refresh_token,'G');
/* convert epoch value to SAS datetime */
call symputx('expires_on',(input(expires_on,best32.)+'01jan1970:00:00'dt),'G');
run;
%mend;

/*
Utility macro that retrieves the initial access token
by redeeming the authorization code that you're granted
during the interactive step using a web browser
while signed into your Microsoft OneDrive / Azure account.

This step also creates the initial token.json that will be
used on subsequent steps/sessions to redeem a refresh token.
*/
%macro get_token(client_id,
code,
resource,
outfile,
redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient,
tenant=common,
debug=0);

proc http url="https://login.microsoft.com/&tenant_id./oauth2/token"
method="POST"
in="%nrstr(&client_id)=&client_id.%nrstr(&code)=&code.%nrstr(&redirect_uri)=&redirect_uri%nrstr(&grant_type)=authorization_code%nrstr(&resource)=&resource."
out=&outfile.;
%if &debug>=0 %then
%do;
debug level=&debug.;
%end;
%else %if &_DEBUG_. ge 1 %then
%do;
debug level=&_DEBUG_.;
%end;
run;

%process_token_file(&outfile);
%mend;

/*
Utility macro to redeem the refresh token
and get a new access token for use in subsequent
calls to the OneDrive service.
*/
%macro refresh(client_id,
refresh_token,
resource,
outfile,
redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient,
tenant=common,
debug=0);

proc http url="https://login.microsoft.com/&tenant_id./oauth2/token"
method="POST"
in="%nrstr(&client_id)=&client_id.%nrstr(&refresh_token=)&refresh_token%nrstr(&redirect_uri)=&redirect_uri.%nrstr(&grant_type)=refresh_token%nrstr(&resource)=&resource."
out=&outfile.;
%if &debug. ge 0 %then
%do;
debug level=&debug.;
%end;
%else %if %symexist(_DEBUG_) AND &_DEBUG_. ge 1 %then
%do;
debug level=&_DEBUG_.;
%end;
run;

%process_token_file(&outfile);
%mend refresh;


%let config_root=/sasdata;
%if %symexist(config_root) %then %do;
  filename config "&config_root./config.json";
  libname config json fileref=config;
  data _null_;
   set config.root;
   call symputx('tenant_id',tenant_id,'G');
   call symputx('client_id',client_id,'G');
   call symputx('redirect_uri',redirect_uri,'G');
   call symputx('resource',resource,'G');
  run;
%end;
%else %do;
  %put ERROR: You must define the CONFIG_ROOT macro variable.; 
%end;
 
options nosource;
%put Paste this URL into your web browser:;
%put -- START -------;
%put &authorize_url;
%put ---END ---------;
options source;
 
filename token "&config_root./token.json";
%let auth_code=0.ASsANoqIDh7i4EKc0r7ZqTPZ7gbAi4275fNDs1zObGSV66vCAJU.AgABAAIAAAAtyolDObpQQ5VtlI4uGjEPAgDs_wUA9P-1LNutY9q_gvE35gtk1lCjx_kyRr_JdW98cdfY4HOk2WnwYICSdhVdRhC1U944v9thQCvL6vCGXeZModAlNAW671OAiYJLJNkm0jsJSJcNLn4icdJ6E0-plge6Uf30nrBiYWXbiO0QTj0BK1mZswVIpmX-I_Ys0T-YKeptfN8byqK2zSHBOR9V3jhgnZeB1qxeGXHf1ocVY2FRJmxSov1JLOGyZ4l3v4SxAKA8Yf2xajjg2913_UNMXdvJ8tGtVceICK2MsO8qoDIBosCUUeOsayFBLndYLocWuh2mCtRfdsWM31zwM_A6Ei7vfYoOeulr-YzTI0e2vxXW-hlBdFXefiiSVfqmH8ePIf0owtmHd9mkZlncm-dPI0e-Os0OXikXlqp2nRLAEFZGuDBkkhsvKJZ_DIaiKUc-Y4R5TR6pFnVUweDpBGIC1gDHEVMJY9yghmX3PBpaw70MQrMS3OVLrnOsr_LnyBLjLPqkN-Mwp8TKiLw9HRE9sVLVVuqvOEc6dQ6mGYC9aBuVfQtjnH6Q7BXpjbZkZOe_zFqMIw9BmlfCGzIWeflDAGHmrFmBplGuEe9qN3f6rfMPpyf65xQ4a5O6Bc5kXB0HlBdH3EH4otJLwiUO5e5bT8DBfcpdGNG5crw0xs3ZHxIUE2l2wHbLPrEXMzzmP57hg5EBZThIniHYxdWQ1PYa0IwnaNRG4t3BUXAiVsq7vS9Tk0X_PWj_A-6S0QzLiHegepwwjnqxamQoLty1_5QfKdM3S8f5OZRogwg1hwEB97-QRRk4Du6dpg0m7H73Mr8srtdcd3mb9XS9hRIRdjloYdgYYvUP0oBj0qQaRM1OqRxo3MYlUAsVrlWPLbuzIxRSCdavLnJ8f-mo1SMo4ET9mIgvtr2GfmBHsHSVbeIZKd62ZMZ_uQb-EDqWb2gDGbWZ0nJWFMxPkA0dGI-8zxN9EgzkH6Layj2koNf1d7tXy4dDGu0cu4dhGFW-DIA7ObZJF8ixNO_sA6Wt_uDa20uY78jTpkjf3uM2Qw0YP0rZSkNcA7TgqLM3Hgk0uTslPtCoElXZbMu7tuocuEULWRz38fj7vrff1LuOVEZ8fHfjB1TJ_iP7KMBO7dd5bCN5Wdd5njGQZMtoSw;
/*
  Now that we have an authorization code we can get the access token
  This step will write the tokens.json file that we can use in our
  production programs.
*/
%get_token(&client_id.,&auth_code,&resource.,token,tenant=&tenant_id);
 
filename token "&config_root./token.json";
 
%process_token_file(token);
 
/* If this is first use for the session, we'll likely need to refresh  */
/* the token.  This will also call process_token_file again and update */
/* our token.json file.                                                */
%refresh(&client_id.,&refresh_token.,&resource.,token,tenant=&tenant_id.);
 
filename resp TEMP;
/* Note: oauth_bearer option added in 9.4M5 */
     oauth_bearer="&access_token"
     out = resp;
run;
 
libname jresp json fileref=resp;

/*Send Mail using microsoft graph*/
/* Define the necessary variables */
%let graph_api_url=https://graph.microsoft.com/v1.0;

%let client_secret=x9y8Q~lA0p5BG~mYdqwDM7FE~ImZIIV5cL9T3cyJ;


/* Define the email message */
%let recipient_email=shivrajpawar49@gmail.com;
%let subject=API SAS;
%let message_body=How you doinn??;

/* Build the JSON payload for the email */
%let email_payload={
"message": {
"subject": "&subject.",
"body": {
"contentType": "Text",
"content": "&message_body."
},
"toRecipients": [
{
"emailAddress": {
"address": "&recipient_email."
}
}
]
}
};

/* Make an HTTP POST request to send the email */
filename response TEMP;
filename request TEMP;
data null;
file request;
put "POST &graph_api_url./users/me/sendMail HTTP/1.1";
put "Host: graph.microsoft.com";
put "Content-Type: application/json";
put "Authorization: Bearer &access_token."; /* Use the access token obtained in your SAS code */
put "Content-Length: " length(&email_payload.);
put;
put &email_payload.;
run;

/* Execute the request and capture the response */
filename cmd pipe 'curl -k -X POST --data @request --header "Authorization: Bearer &access_token." &graph_api_url./users/me/sendMail';
data null;
infile cmd length=len;
input;
file response;
put input;
run;

/* Check the response for any errors */
data null;
infile response;
input;
put infile;
run;
2 REPLIES 2
ballardw
Super User

So what part are you having problems with?
Since your code includes macros you should set:

options mprint;

before running the macros to capture the code generated by them.


Are there errors in the log?: Post the code and log in a code box opened with the "</>" to maintain formatting of error messages.

No output? Post any log in a code box.

Unexpected output? Provide input data in the form of data step code pasted into a code box, the actual results and the expected results. Instructions here: https://communities.sas.com/t5/SAS-Communities-Library/How-to-create-a-data-step-version-of-your-dat... will show how to turn an existing SAS data set into data step code that can be pasted into a forum code box using the "</>" icon or attached as text to show exactly what you have and that we can test code against.

Rutujak
Calcite | Level 5

 ERROR   

{"error":{"code":"OrganizationFromTenantGuidNotFound","message":"The tenant for tenant guid '0e888a36-e21e-42e0-9cd2-bed9a933d9ee' d
oes not exist.","innerError":{"oAuthEventOperationId":"173eae30-7d2d-47fe-a7c2-89eaea6d822c","oAuthEventcV":"4BaJutuPZOOnZzJRr+gHNg.
1.1.1","errorUrl":"https://aka.ms/autherrors#error-InvalidTenant","requestId":"434b4ffd-4897-4dd0-84db-9b6cfd211a69","d...
-13T05:16:41"}}} 

 After a Modified Code

 

/*
Utility macro to process the JSON token
file that was created at authorization time.
This will fetch the access token, refresh token,
and expiration datetime for the token so we know
if we need to refresh it.
*/
%macro process_token_file(file);
libname oauth json fileref=&file.;

data _null_;
set oauth.root;
call symputx('access_token', access_token,'G');
call symputx('refresh_token', refresh_token,'G');
/* convert epoch value to SAS datetime */
call symputx('expires_on',(input(expires_on,best32.)+'01jan1970:00:00'dt),'G');
run;
%mend;

/*
Utility macro that retrieves the initial access token
by redeeming the authorization code that you're granted
during the interactive step using a web browser
while signed into your Microsoft OneDrive / Azure account.

This step also creates the initial token.json that will be
used on subsequent steps/sessions to redeem a refresh token.
*/
%macro get_token(client_id,
client_secret,
code,
scope,
outfile,
redirect_uri=https://deployopt.demo.com/SASLogon/login/callback/azure,
tenant=0e888a36-e21e-42e0-9cd2-bed9a933d9ee,
debug=0);

proc http url="https://login.microsoftonline.com/0e888a36-e21e-42e0-9cd2-bed9a933d9ee/oauth2/v2.0/token"
method="POST"
in="%nrstr(&client_id)=&client_id.%nrstr(&client_secret)=&client_secret.%nrstr(&code)=&code.%nrstr(&redirect_uri)=&redirect_uri%nrstr(&grant_type)=authorization_code%nrstr(&scope)=&scope."
out=&outfile.;
%if &debug>=0 %then
%do;
debug level=&debug.;
%end;
%else %if &_DEBUG_. ge 1 %then
%do;
debug level=&_DEBUG_.;
%end;
run;

%process_token_file(&outfile);
%mend;

/*
Utility macro to redeem the refresh token
and get a new access token for use in subsequent
calls to the OneDrive service.
*/
%macro refresh(client_id,
client_secret,
refresh_token,
scope,
outfile,
redirect_uri=https://deployopt.demo.com/SASLogon/login/callback/azure,
tenant=common,
debug=0);

proc http url="https://login.microsoftonline.com/0e888a36-e21e-42e0-9cd2-bed9a933d9ee/oauth2/v2.0/token"
method="POST"
in="%nrstr(&client_id)=&client_id.%nrstr(&client_secret)=&client_secret.%nrstr(&refresh_token=)&refresh_token%nrstr(&redirect_uri)=&redirect_uri.%nrstr(&grant_type)=refresh_token%nrstr(&scope)=&scope."
out=&outfile.;
%if &debug. ge 0 %then
%do;
debug level=&debug.;
%end;
%else %if %symexist(_DEBUG_) AND &_DEBUG_. ge 1 %then
%do;
debug level=&_DEBUG_.;
%end;
run;

%process_token_file(&outfile);
%mend refresh;

/* generate Token */

%let config_root=/sasdata;
%if %symexist(config_root) %then %do;
  filename config "&config_root./config.json";
  libname config json fileref=config;
  data _null_;
   set config.root;
   call symputx('tenant_id',tenant_id,'G');
   call symputx('client_id',client_id,'G');
   call symputx('redirect_uri',redirect_uri,'G');
   call symputx('client_secret',client_secret,'G');
   call symputx('scope',scope,'G');
  run;
%end;
%else %do;
  %put ERROR: You must define the CONFIG_ROOT macro variable.; 
%end;
 
options nosource;
%put Paste this URL into your web browser:;
%put -- START -------;
%put &authorize_url;
%put ---END ---------;
options source;
 
filename token "&config_root./token.json";
%let auth_code=0.ASsANoqIDh7i4EKc0r7ZqTPZ7jwymLltqM1Ao9E-7URpiinCAAA.AgABAAIAAAAtyolDObpQQ5VtlI4uGjEPAgDs_wUA9P-O2Dj3uaJl1brAZnNrFCJl0Hbmyd-N3gb2ToMjb4L5Xd8LSegqzaKJ1hajRaRrCmvxtFsBeNe-ukY7q8ZOGOu5iOMZFsUm8sBzLX6gYc_5KECWoNNwOMj5szizeiryEpfSlArSy1P2uq9mzfmn-umXIEyOqh73Tuo48hNWhfgHgAzFZSGw5u4Pe-0wxnyByueERkxo6qI8AZVWDsoppwsFDwxX0RTIsgsj0RI_mpzqYYJYU4wFDkSjDImVfeXQbDOE_ToL8CFLuF7m_Op513_4FJOqZNjZ24U2xN3k-BIAvzpVVwfFi1YTdnmQvgwHV7SUF4oV89rRvj9EYZdXMWcP_PyTpmzsV-cByGR1DC2FKYajWUSnAp8OOq5-VM6DG8g43J-8_RT9ggnxSILHsLYUR8eWWCnwiCA6xeJ4rd25Esd4YFhMejOnVFwvqRyteY1f1ORDV2r90Kyu735eg02qid5UnfQC32cXmsAv8PnLzDKUWM_hsOa-dT0yFaqP8jjuqbzkwZrFFWaVG1TJwB0YBxtp6faDxnlmkyF_ce3dIp3Swzml_dOMtobBzYtTDUO5gnDUmSDpiyoDSupEJuTTzBv-7-05h1Tf9UfZeehytnFqXfKam_AYEN39xoYDPVZr3l6l97ike3nzcP7JUvQT2k6pehXMI9mw397EScUBj4qFwU9slSbL9IYMZMS4kXYOIWEzm2vqUABmFTPUSJtii4xuRozW8k2WTeoxC0o6UvE7N93KZ2rtAOwhW_6VZuQSHPR_-ET37Yv8HjL9-ihi7mYvi9dVHjbPCHkVPT4SO17wZBOpGWqJ4F470OkrqAu-8FeEurQ8RNEAudqdHq7qCrJBvGo2zmphaRZi-n0Tk-8C-6QvjaJisrbSU-Hgpc2ZkNp2ODSnOlOV4_l5rCzLWIMon_XaHJipUWFZtjLe_DlcGZjVdY7R6qFo_vSuas3GH94xO--qiiYbOzTtTQp-uPKaTQvc5DWhhEzTmYEtkXMdMJVfzbdtoKDtIFSxPugHFwTlEnjqmOHlc9x-3EiASbAP3gaYku8yWSdWCR-k951vgtpKGb0E1zuIbresyQ_awkjfxO9QiKwF0NsXaudV7r7sNWQ-id1GPha0JBGYP9wsxFJFHjOTBAhldWa-D7Ij0H73_oyGqBad3VtFx1Ar6M23jzdDQTS1klDtVd48ai0JaTd6vclYR_M-CFnqn3oGkHYLC2aLUCn9Wmg4_1juireRu2WxSW7Z1f4E1CtXWTEC0cY58g5osocbVl57yw;
/*
  Now that we have an authorization code we can get the access token
  This step will write the tokens.json file that we can use in our
  production programs.
*/
%get_token(&client_id.,&client_secret.,&auth_code,&scope.,token,tenant=&tenant_id);
 
filename token "&config_root./token.json";
 
%process_token_file(token);
 
/* If this is first use for the session, we'll likely need to refresh  */
/* the token.  This will also call process_token_file again and update */
/* our token.json file.                                                */
%refresh(&client_id.,&refresh_token.,&resource.,token,tenant=&tenant_id.);
 
filename resp TEMP;
/* Note: oauth_bearer option added in 9.4M5 */
     oauth_bearer="&access_token"
     out = resp;
run;
 
libname jresp json fileref=resp;

/* send MAil*/
 

%let EMAIL_ADDRESS = shivraj.pawar@woodpeckerind.com;
filename send temp;
data null;
file send;
put '{"message": {'
'"subject": "Test Email",'
'"body": {'
'"contentType": "Text",'
'"content": "This is a test email."'
'},'
'"toRecipients": ['
'{'
'"emailAddress": {'
'"address": "' "&EMAIL_ADDRESS." '"'
'}'
'}'
']'
'}}';
run;

filename resp temp;
proc http
method="POST"
url="https://graph.microsoft.com/v1.0/me/sendMail"
in=send
out=resp; /* Store the response in a file */
headers "Content-Type"="application/json"
"Authorization"="Bearer &access_token.";
run;

/* Check the response for success or error */
data _null_;
infile resp;
input;
put _infile_;
run;