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