Production Ready Macros for SAS Application Developers
https://github.com/sasjs/core
mv_registerclient.sas
Go to the documentation of this file.
1 /**
2  @file mv_registerclient.sas
3  @brief Register Client and Secret (admin task)
4  @details When building apps on SAS Viya, an client id and secret is required.
5  This macro will obtain the Consul Token and use that to call the Web Service.
6 
7  more info: https://developer.sas.com/reference/auth/#register
8  and: http://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches/
9 
10  The default viyaroot location is /opt/sas/viya/config
11 
12  Usage:
13 
14  %* compile macros;
15  filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
16  %inc mc;
17 
18  %* specific client with just openid scope;
19  %mv_registerclient(client_id=YourClient
20  ,client_secret=YourSecret
21  ,scopes=openid
22  )
23 
24  %* generate random client details with all scopes;
25  %mv_registerclient(scopes=openid *)
26 
27  %* generate random client with 90/180 second access/refresh token expiry;
28  %mv_registerclient(scopes=openid *
29  ,access_token_validity=90
30  ,refresh_token_validity=180
31  )
32 
33  @param client_id= The client name. Auto generated if blank.
34  @param client_secret= Client secret Auto generated if client is blank.
35  @param scopes= list of space-seperated unquoted scopes (default is openid)
36  @param grant_type= valid values are "password" or "authorization_code" (unquoted)
37  @param outds= the dataset to contain the registered client id and secret
38  @param access_token_validity= The duration of validity of the access token
39  in seconds. A value of DEFAULT will omit the entry (and use system default)
40  @param refresh_token_validity= The duration of validity of the refresh token
41  in seconds. A value of DEFAULT will omit the entry (and use system default)
42  @param name= A human readable name for the client
43  @param required_user_groups= A list of group names. If a user does not belong
44  to all the required groups, the user will not be authenticated and no tokens
45  are issued to this client for that user. If this field is not specified,
46  authentication and token issuance proceeds normally.
47  @param autoapprove= During the auth step the user can choose which scope to
48  apply. Setting this to true will autoapprove all the client scopes.
49  @param use_session= If true, access tokens issued to this client will be
50  associated with an HTTP session and revoked upon logout or time-out.
51  @param outjson= A dataset containing the lines of JSON submitted. Useful
52  for debugging. Default= _null_.
53 
54  @version VIYA V.03.04
55  @author Allan Bowe, source: https://github.com/sasjs/core
56 
57  <h4> SAS Macros </h4>
58  @li mp_abort.sas
59  @li mf_getplatform.sas
60  @li mf_getuniquefileref.sas
61  @li mf_getuniquelibref.sas
62  @li mf_loc.sas
63  @li mf_getquotedstr.sas
64  @li mf_getuser.sas
65 
66 **/
67 
68 %macro mv_registerclient(client_id=
69  ,client_secret=
70  ,client_name=DEFAULT
71  ,scopes=openid
72  ,grant_type=authorization_code|refresh_token
73  ,required_user_groups=
74  ,autoapprove=
75  ,use_session=
76  ,outds=mv_registerclient
77  ,access_token_validity=DEFAULT
78  ,refresh_token_validity=DEFAULT
79  ,outjson=_null_
80  );
81 %local consul_token fname1 fname2 fname3 libref access_token url;
82 
83 %if client_name=DEFAULT %then %let client_name=
84  Generated by %mf_getuser() on %sysfunc(datetime(),datetime19.) using SASjs;
85 
86 options noquotelenmax;
87 /* first, get consul token needed to get client id / secret */
88 data _null_;
89  infile "%mf_loc(VIYACONFIG)/etc/SASSecurityCertificateFramework/tokens/consul/default/client.token";
90  input token:$64.;
91  call symputx('consul_token',token);
92 run;
93 
94 %local base_uri; /* location of rest apis */
95 %let base_uri=%mf_getplatform(VIYARESTAPI);
96 
97 /* request the client details */
98 %let fname1=%mf_getuniquefileref();
99 proc http method='POST' out=&fname1
100  url="&base_uri/SASLogon/oauth/clients/consul?callback=false%str(&)serviceId=app";
101  headers "X-Consul-Token"="&consul_token";
102 run;
103 
104 %let libref=%mf_getuniquelibref();
105 libname &libref JSON fileref=&fname1;
106 
107 /* extract the token */
108 data _null_;
109  set &libref..root;
110  call symputx('access_token',access_token,'l');
111 run;
112 
113 /**
114  * register the new client
115  */
116 %let fname2=%mf_getuniquefileref();
117 %if x&client_id.x=xx %then %do;
118  %let client_id=client_%sysfunc(ranuni(0),hex16.);
119  %let client_secret=secret_%sysfunc(ranuni(0),hex16.);
120 %end;
121 
122 %let scopes=%sysfunc(coalescec(&scopes,openid));
123 %let scopes=%mf_getquotedstr(&scopes,QUOTE=D,indlm=|);
124 %let grant_type=%mf_getquotedstr(&grant_type,QUOTE=D,indlm=|);
125 %let required_user_groups=%mf_getquotedstr(&required_user_groups,QUOTE=D,indlm=|);
126 
127 data _null_;
128  file &fname2;
129  length clientid clientsecret clientname scope grant_types reqd_groups
130  autoapprove $256.;
131  clientid='"client_id":'!!quote(trim(symget('client_id')));
132  clientsecret=',"client_secret":'!!quote(trim(symget('client_secret')));
133  clientname=',"name":'!!quote(trim(symget('client_name')));
134  scope=',"scope":['!!symget('scopes')!!']';
135  grant_types=symget('grant_type');
136  if grant_types = '""' then grant_types ='';
137  grant_types=cats(',"authorized_grant_types": [',grant_types,']');
138  reqd_groups=symget('required_user_groups');
139  if reqd_groups = '""' then reqd_groups ='';
140  else reqd_groups=cats(',"required_user_groups":[',reqd_groups,']');
141  autoapprove=trim(symget('autoapprove'));
142  if not missing(autoapprove) then autoapprove=cats(',"autoapprove":',autoapprove);
143  use_session=trim(symget('use_session'));
144  if not missing(use_session) then use_session=cats(',"use_session":',use_session);
145 
146  put '{' clientid ;
147  put clientsecret ;
148  put clientname;
149  put scope;
150  put grant_types;
151  if not missing(reqd_groups) then put reqd_groups;
152  put autoapprove;
153  put use_session;
154 %if &access_token_validity ne DEFAULT %then %do;
155  put ',"access_token_validity":' "&access_token_validity";
156 %end;
157 %if &refresh_token_validity ne DEFAULT %then %do;
158  put ',"refresh_token_validity":' "&refresh_token_validity";
159 %end;
160 
161  put ',"redirect_uri": "urn:ietf:wg:oauth:2.0:oob"';
162  put '}';
163 run;
164 
165 %let fname3=%mf_getuniquefileref();
166 proc http method='POST' in=&fname2 out=&fname3
167  url="&base_uri/SASLogon/oauth/clients";
168  headers "Content-Type"="application/json"
169  "Authorization"="Bearer &access_token";
170 run;
171 
172 /* show response */
173 %local err;
174 %let err=NONE;
175 data _null_;
176  infile &fname3;
177  input;
178  if _infile_=:'{"err'!!'or":' then do;
179  length message $32767;
180  message=scan(_infile_,-2,'"');
181  call symputx('err',message,'l');
182  end;
183 run;
184 %if "&err" ne "NONE" %then %do;
185  %put %str(ERR)OR: &err;
186 %end;
187 
188 /* prepare url */
189 %if %index(%superq(grant_type),authorization_code) %then %do;
190  data _null_;
191  if symexist('_baseurl') then do;
192  url=symget('_baseurl');
193  if subpad(url,length(url)-9,9)='SASStudio'
194  then url=substr(url,1,length(url)-11);
195  else url="&systcpiphostname";
196  end;
197  else url="&systcpiphostname";
198  call symputx('url',url);
199  run;
200 %end;
201 
202 %put Please provide the following details to the developer:;
203 %put ;
204 %put CLIENT_ID=&client_id;
205 %put CLIENT_SECRET=&client_secret;
206 %put GRANT_TYPE=&grant_type;
207 %put;
208 %if %index(%superq(grant_type),authorization_code) %then %do;
209  /* cannot use base_uri here as it includes the protocol which may be incorrect externally */
210  %put NOTE: The developer must also register below and select 'openid' to get the grant code:;
211  %put NOTE- ;
212  %put NOTE- &url/SASLogon/oauth/authorize?client_id=&client_id%str(&)response_type=code;
213  %put NOTE- ;
214 %end;
215 
216 data &outds;
217  client_id=symget('client_id');
218  client_secret=symget('client_secret');
219  error=symget('err');
220 run;
221 
222 data &outjson;
223  infile &fname2;
224  input;
225  line=_infile_;
226 run;
227 
228 /* clear refs */
229 filename &fname1 clear;
230 filename &fname2 clear;
231 filename &fname3 clear;
232 libname &libref clear;
233 
234 %mend;