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, a client id and secret are usually
5  required. In order to generate them, the Consul Token is required. To access
6  this token, you need to be a system administrator (it is not enough to be in
7  the SASAdministrator group in SAS Environment Manager).
8 
9  If you are registering a lot of clients / secrets, you may find it more
10  convenient to use the [Viya Token Generator]
11  (https://sasjs.io/apps/#viya-client-token-generator) (a SASjs Web App to
12  automate the generation of clients & secrets with various settings).
13 
14  For further information on clients / secrets, see;
15  @li https://developer.sas.com/reference/auth/#register
16  @li https://proc-x.com/2019/01/authentication-to-sas-viya-a-couple-of-approaches
17  @li https://cli.sasjs.io/faq/#how-can-i-obtain-a-viya-client-and-secret
18 
19  The default viyaroot location is: `/opt/sas/viya/config`
20 
21  Usage:
22 
23  %* compile macros;
24  filename mc url
25  "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
26  %inc mc;
27 
28  %* generate random client details with openid scope;
29  %mv_registerclient(scopes=openid )
30 
31  %* generate random client using consul token as input parameter;
32  %mv_registerclient(consul_token=12x34sa43v2345n234lasd)
33 
34  %* specific client with just openid scope;
35  %mv_registerclient(client_id=YourClient
36  ,client_secret=YourSecret
37  ,scopes=openid
38  )
39 
40  %* generate random client with 90/180 second access/refresh token expiry;
41  %mv_registerclient(scopes=openid
42  ,access_token_validity=90
43  ,refresh_token_validity=180
44  )
45 
46  @param [in,out] client_id= The client name. Auto generated if blank.
47  @param [in,out] client_secret= Client secret. Auto generated if client is
48  blank.
49  @param [in] consul_token= (0) Provide the actual consul token value here if
50  using Viya 4 or above.
51  @param [in] scopes= (openid) List of space-seperated unquoted scopes
52  @param [in] grant_type= (authorization_code|refresh_token) Valid values are
53  "password" or "authorization_code" (unquoted). Pipe seperated.
54  @param [out] outds=(mv_registerclient) The dataset to contain the registered
55  client id and secret
56  @param [in] access_token_validity= (DEFAULT) The access token duration in
57  seconds. A value of DEFAULT will omit the entry (and use system default)
58  @param [in] refresh_token_validity= (DEFAULT) The duration of validity of the
59  refresh token in seconds. A value of DEFAULT will omit the entry (and use
60  system default)
61  @param [in] client_name= (DEFAULT) An optional, human readable name for the
62  client.
63  @param [in] required_user_groups= A list of group names. If a user does not
64  belong to all the required groups, the user will not be authenticated and no
65  tokens are issued to this client for that user. If this field is not
66  specified, authentication and token issuance proceeds normally.
67  @param [in] autoapprove= During the auth step the user can choose which scope
68  to apply. Setting this to true will autoapprove all the client scopes.
69  @param [in] use_session= If true, access tokens issued to this client will be
70  associated with an HTTP session and revoked upon logout or time-out.
71  @param [out] outjson= (_null_) A dataset containing the lines of JSON
72  submitted. Useful for debugging.
73 
74  @version VIYA V.03.04
75  @author Allan Bowe, source: https://github.com/sasjs/core
76 
77  <h4> SAS Macros </h4>
78  @li mf_getplatform.sas
79  @li mf_getuniquefileref.sas
80  @li mf_getuniquelibref.sas
81  @li mf_loc.sas
82  @li mf_getquotedstr.sas
83  @li mf_getuser.sas
84  @li mp_abort.sas
85 
86 **/
87 
88 %macro mv_registerclient(client_id=
89  ,client_secret=
90  ,consul_token=0
91  ,client_name=DEFAULT
92  ,scopes=openid
93  ,grant_type=authorization_code|refresh_token
94  ,required_user_groups=
95  ,autoapprove=
96  ,use_session=
97  ,outds=mv_registerclient
98  ,access_token_validity=DEFAULT
99  ,refresh_token_validity=DEFAULT
100  ,outjson=_null_
101  );
102 %local fname1 fname2 fname3 libref access_token url tokloc msg;
103 
104 %if client_name=DEFAULT %then %let client_name=
105  Generated by %mf_getuser() (&sysuserid) on %sysfunc(datetime(),datetime19.
106  ) using SASjs;
107 
108 options noquotelenmax;
109 
110 %if "&consul_token"="0" %then %do;
111  /* first, get consul token needed to get client id / secret */
112  %let tokloc=/etc/SASSecurityCertificateFramework/tokens/consul/default;
113  %let tokloc=%mf_loc(VIYACONFIG)&tokloc/client.token;
114 
115  %if %sysfunc(fileexist(&tokloc))=0 %then %do;
116  %let msg=Unable to access the consul token at &tokloc;
117  %put &sysmacroname: &msg;
118  %put Try passing the value in the consul= macro parameter;
119  %put See docs: https://core.sasjs.io/mv__registerclient_8sas.html;
120  %mp_abort(mac=mv_registerclient,msg=%str(&msg))
121  %end;
122 
123  data _null_;
124  infile "&tokloc";
125  input token:$64.;
126  call symputx('consul_token',token);
127  run;
128 
129  %if "&consul_token"="0" %then %do;
130  %put &sysmacroname: Unable to source the consul token from &tokloc;
131  %put It seems your account (&sysuserid) does not have admin rights;
132  %put Please speak with your platform adminstrator;
133  %put Docs: https://core.sasjs.io/mv__registerclient_8sas.html;
134  %abort;
135  %end;
136 %end;
137 
138 %local base_uri; /* location of rest apis */
139 %let base_uri=%mf_getplatform(VIYARESTAPI);
140 
141 /* request the client details */
142 %let fname1=%mf_getuniquefileref();
143 proc http method='POST' out=&fname1
144  url="&base_uri/SASLogon/oauth/clients/consul?callback=false%str(&)%trim(
145  )serviceId=app";
146  headers "X-Consul-Token"="&consul_token";
147 run;
148 
149 %put &=SYS_PROCHTTP_STATUS_CODE;
150 %put &=SYS_PROCHTTP_STATUS_PHRASE;
151 
152 %let libref=%mf_getuniquelibref();
153 libname &libref JSON fileref=&fname1;
154 
155 /* extract the token */
156 data _null_;
157  set &libref..root;
158  call symputx('access_token',access_token,'l');
159 run;
160 
161 /**
162  * register the new client
163  */
164 %let fname2=%mf_getuniquefileref();
165 %if x&client_id.x=xx %then %do;
166  %let client_id=client_%sysfunc(ranuni(0),hex16.);
167  %let client_secret=secret_%sysfunc(ranuni(0),hex16.);
168 %end;
169 
170 %let scopes=%sysfunc(coalescec(&scopes,openid));
171 %let scopes=%mf_getquotedstr(&scopes,QUOTE=D,indlm=|);
172 %let grant_type=%mf_getquotedstr(&grant_type,QUOTE=D,indlm=|);
173 %let required_user_groups=
174  %mf_getquotedstr(&required_user_groups,QUOTE=D,indlm=|);
175 
176 data _null_;
177  file &fname2;
178  length clientid clientsecret clientname scope grant_types reqd_groups
179  autoapprove $256.;
180  clientid='"client_id":'!!quote(trim(symget('client_id')));
181  clientsecret=',"client_secret":'!!quote(trim(symget('client_secret')));
182  clientname=',"name":'!!quote(trim(symget('client_name')));
183  scope=',"scope":['!!symget('scopes')!!']';
184  grant_types=symget('grant_type');
185  if grant_types = '""' then grant_types ='';
186  grant_types=cats(',"authorized_grant_types": [',grant_types,']');
187  reqd_groups=symget('required_user_groups');
188  if reqd_groups = '""' then reqd_groups ='';
189  else reqd_groups=cats(',"required_user_groups":[',reqd_groups,']');
190  autoapprove=trim(symget('autoapprove'));
191  if not missing(autoapprove) then autoapprove=
192  cats(',"autoapprove":',autoapprove);
193  use_session=trim(symget('use_session'));
194  if not missing(use_session) then use_session=
195  cats(',"use_session":',use_session);
196 
197  put '{' clientid ;
198  put clientsecret ;
199  put clientname;
200  put scope;
201  put grant_types;
202  if not missing(reqd_groups) then put reqd_groups;
203  put autoapprove;
204  put use_session;
205 %if &access_token_validity ne DEFAULT %then %do;
206  put ',"access_token_validity":' "&access_token_validity";
207 %end;
208 %if &refresh_token_validity ne DEFAULT %then %do;
209  put ',"refresh_token_validity":' "&refresh_token_validity";
210 %end;
211 
212  put ',"redirect_uri": "urn:ietf:wg:oauth:2.0:oob"';
213  put '}';
214 run;
215 
216 %let fname3=%mf_getuniquefileref();
217 proc http method='POST' in=&fname2 out=&fname3
218  url="&base_uri/SASLogon/oauth/clients";
219  headers "Content-Type"="application/json"
220  "Authorization"="Bearer &access_token";
221 run;
222 
223 /* show response */
224 %local err;
225 %let err=NONE;
226 data _null_;
227  infile &fname3;
228  input;
229  if _infile_=:'{"err'!!'or":' then do;
230  length message $32767;
231  message=scan(_infile_,-2,'"');
232  call symputx('err',message,'l');
233  end;
234 run;
235 %if "&err" ne "NONE" %then %do;
236  %put %str(ERR)OR: &err;
237 %end;
238 
239 /* prepare url */
240 %if %index(%superq(grant_type),authorization_code) %then %do;
241  data _null_;
242  if symexist('_baseurl') then do;
243  url=symget('_baseurl');
244  if subpad(url,length(url)-9,9)='SASStudio'
245  then url=substr(url,1,length(url)-11);
246  else url="&systcpiphostname";
247  end;
248  else url="&systcpiphostname";
249  call symputx('url',url);
250  run;
251 %end;
252 
253 %put Please provide the following details to the developer:;
254 %put ;
255 %put CLIENT_ID=&client_id;
256 %put CLIENT_SECRET=&client_secret;
257 %put GRANT_TYPE=&grant_type;
258 %put;
259 %if %index(%superq(grant_type),authorization_code) %then %do;
260  /* cannot use base_uri here as it includes the protocol which may be incorrect
261  externally */
262  %put NOTE: Visit the link below and select 'openid' to get the grant code:;
263  %put NOTE- ;
264  %put NOTE- &url/SASLogon/oauth/authorize?client_id=&client_id%str(&)%trim(
265  )response_type=code;
266  %put NOTE- ;
267 %end;
268 
269 data &outds;
270  client_id=symget('client_id');
271  client_secret=symget('client_secret');
272  error=symget('err');
273 run;
274 
275 data &outjson;
276  infile &fname2;
277  input;
278  line=_infile_;
279 run;
280 
281 /* clear refs */
282 filename &fname1 clear;
283 filename &fname2 clear;
284 filename &fname3 clear;
285 libname &libref clear;
286 
287 %mend mv_registerclient;