Display only endpoints available to user in Swagger after his login - swagger

I would like to setup the follownig workflow:
Initially, without login, Swagger shows only 2-3 endpoints - this will be done by providing limited openapi3 json from backend, no problem;
User logs in via Authorize button (works, openapi3 json has necessary info);
After login, Swagger emits one more request with user credentials, backend provides new openapi3 json with endpoints available to this specific user and Swagger redraws the page with new data. Preferably, user is still logged in.
Is it possible to do Item 3 with Swagger? How can I manually emit request from Swagger with OAuth2 bearer token (since user logged, token must present somwhere) and redraw Swagger page?

The task was done via Swagger customization using its plugin system.
Actually Swagger is a JavaScript (Babel, Webpack) project using React / Redux and it was a little bit hard to dig into it since I do not know React (my tool is Python) but finally I managed.
Here is the code for my custom plugin with comments:
const AuthorizedPlugin = function(system) {
return {
statePlugins: {
auth: { // namespace for authentication subsystem
// last components invoked after authorization or logout are
// so-called reducers, exactly they are responsible for page redraw
reducers: {
"authorize_oauth2": function(state, action) {
let { auth, token } = action.payload
let parsedAuth
auth.token = Object.assign({}, token)
parsedAuth = system.Im.fromJS(auth)
var req = {
credentials: 'same-origin',
headers: {
accept: "application/json",
Authorization: "Bearer " + auth.token.access_token
},
method: 'GET',
url: system.specSelectors.url()
}
// this is the additional request with token I mentioned in the question
system.fn.fetch(req).then(
function (result) {
// ... and we just call updateSpec with new openapi json
system.specActions.updateSpec(result.text)
}
)
// This line is from the original Swagger-ui code
return state.setIn( [ "authorized", parsedAuth.get("name") ], parsedAuth )
},
"logout": function(state, action) {
var req = {
credentials: 'same-origin',
headers: { accept: "application/json" },
method: 'GET',
url: system.specSelectors.url()
}
// for logout, request does not contain authorization token
system.fn.fetch(req).then(
function (result) {
system.specActions.updateSpec(result.text)
}
)
// these lines are to make lock symbols gray and remove credentials
var result = state.get("authorized").withMutations(function (authorized) {
action.payload.forEach(function (auth) {
authorized.delete(auth);
});
});
return state.set("authorized", result)
}
}
}
}
}
}
Insert this plugin as usual:
const ui = SwaggerUIBundle({{
url: '{openapi_url}',
dom_id: '#swagger-ui',
defaultModelsExpandDepth: -1,
displayOperationId: false,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
plugins: [
AuthorizedPlugin
],
layout: "BaseLayout",
deepLinking: true
})

Related

How to share cookies between in-app browser and React Native (iOS)?

I am trying to implement SSO in a mobile app.
I am using react-native-inappbrowser-reborn to handle the SSO auth flow in an in-app browser. I can successfully authenticate inside the in-app browser. That is to say, I receive a session cookie and I can view the web version of the app from inside of the in-app browser. However, when I redirect back to the mobile application, none of my fetch requests include the session cookie! I have fetch configured with {credentials: 'include'}. CookieManager.getAll() returns an empty object.
I am experiencing this problem on iOS (v15.2), and I have yet to test on Android.
According to the documentation I should be able to share the cookie set in the in the in-app browser with my react-native app.
I am using the following code based off of the react-native-inappbrowser-reborn documentation
async function authenticate() {
const url = 'http://localhost:3000/sso/login?redirect_uri=myapp://home';
const deepLink = 'myapp://home';
try {
if (await InAppBrowser.isAvailable()) {
InAppBrowser.openAuth(url, deepLink, {
// iOS Properties
ephemeralWebSession: false,
// Android Properties
showTitle: false,
enableUrlBarHiding: true,
enableDefaultShare: false,
}).then(async (response) => {
if (response.type === 'success' && response.url) {
console.log('should see a new cookie set!');
console.log(await CookieManager.getAll()); // Sadly, nothing in here
const user = await fetch('http://localhost:3000/user', {
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
/* This is an authenticated endpoint which returns a 401,
because for some reason the session cookie doesn't go along with the request */
}
});
} else {
throw new Error('login unsuccessful');
}
} catch (error) {
throw new Error();
}
}
Any bit of insight here would be immensely appreciated!

zapier performResume step not being waited for / run

I'm following the docs in zapier regarding the callbackUrl https://platform.zapier.com/cli_docs/docs#zgeneratecallbackurl however cannot seem to get the performResume step to be run. The zap I'm creating based on this integration also does not seem to wait for the callbackUrl to be hit.
const createScreenshot = (z, bundle) => {
const callbackUrl = z.generateCallbackUrl();
const promise = z.request({
url: 'https://myapi.com/v1/render',
method: 'POST',
params: {},
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: {
...bundle.inputData,
webhook_url: callbackUrl
},
removeMissingValuesFrom: {},
});
z.console.log("Returning from perform / createScreenshot");
return promise.then((response) => ({ ...response.data, waiting_for: "performResume" }));
const onScreenshotFinished = (z, bundle) => {
z.console.log("In performResume / onScreenshotFinished", bundle.cleanedRequest);
const responseBody = bundle.cleanedRequest;
let screenshotUrl;
if (responseBody.event === "render.succeeded") {
z.console.log("render was processed successfully", responseBody);
screenshotUrl = responseBody.result.renderUrl;
return { screenshotUrl, ...responseBody };
}
z.console.log("render was not processed", responseBody);
throw z.errors.Error("Screenshot was not successful");
}
module.exports = {
operation: {
perform: createScreenshot,
performResume: onScreenshotFinished,
...
}
}
We talked through this question (and its solution) on GitHub (zapier/zapier-platform#398), but to summarize for SO readers:
When setting up a resumable Zap, the editor uses the sample to populate the data in the callback. No actual waiting happens during the setup process. Once the zap is live, it works like normal.
So, to implement:
perform should return sample data that matches the data the "resume" webhook sends
performSubscribe can read that data and operate normally
See the GH issue for more info.

Is possible to automate OAuth 2.0 implicit grant flow of Azure active directory in postman?

I'm trying to automate all the testing of an API. Currently is using a utentificacion using AAD.
The problem is: I can use the process of postman to get the token using OAuth2.0
Postman dialog
but I can't run a collection and do something like a trigger to get the token at the beginning. If i want to take the token I must push the button "Get new access token"
there is some way to do it automatically? or how can I create a flow to obtain the token?
Thanks!
You could use Pre-request Script to do it automatically. You just need to modify your required value and post it in the Pre-request Script of the postman. It had better in the parent collection, so it could inherit auth from the parent.
var getToken = true;
if (!pm.environment.get('accessTokenExpiry') ||
!pm.environment.get('currentAccessToken')) {
console.log('Token or expiry date are missing')
} else if (pm.environment.get('accessTokenExpiry') <= (new Date()).getTime()) {
console.log('Token is expired')
} else {
getToken = false;
console.log('Token and expiry date are all good');
}
if (getToken === true) {
pm.sendRequest({
url: 'https://login.microsoftonline.com/microsoft.onmicrosoft.com/oauth2/token',
method: 'POST',
header: 'Content-Type:application/x-www-form-urlencoded',
body: {
mode: 'raw',
raw: 'grant_type=implicit&client_id...'
}
}, function (err, res) {
console.log(err ? err : res.json());
if (err === null) {
console.log('Saving the token and expiry date')
var responseJson = res.json();
pm.environment.set('currentAccessToken', responseJson.access_token)
var expiryDate = new Date();
expiryDate.setSeconds(expiryDate.getSeconds() + responseJson.expires_in);
pm.environment.set('accessTokenExpiry', expiryDate.getTime());
}
});
}
For the code sample, you could refer to here.

Client-side retrieval of Google Contact pictures

I'm fetching google contacts in a webapp using the Google JavaScript API and I'd like to retrieve their pictures.
I'm doing something like this (heavily simplified):
var token; // let's admit this is available already
function getPhotoUrl(entry, cb) {
var link = entry.link.filter(function(link) {
return link.type.indexOf("image") === 0;
}).shift();
if (!link)
return cb(null);
var request = new XMLHttpRequest();
request.open("GET", link.href + "?v=3.0&access_token=" + token, true);
request.responseType = "blob";
request.onload = cb;
request.send();
}
function onContactsLoad(responseText) {
var data = JSON.parse(responseText);
(data.feed.entry || []).forEach(function(entry) {
getPhotoUrl(e, function(a, b, c) {
console.log("pic", a, b, c);
});
});
}
But I'm getting this error both in Chrome and Firefox:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://www.google.com/m8/feeds/photos/media/<user_email>/<some_contact_id>?v=3.0&access_token=<obfuscated>. This can be fixed by moving the resource to the same domain or enabling CORS.
When looking at the response headers from the feeds/photos endpoint, I can see that Access-Control-Allow-Origin: * is not sent, hence the CORS error I get.
Note that Access-Control-Allow-Origin: * is sent when reaching the feeds/contacts endpoint, hence allowing cross-domain requests.
Is this a bug, or did I miss something from their docs?
Assuming you only need the "profile picture", try actually moving the request for that image directly into HTML, by setting a full URL as the src element of an <img> tag (with a ?access_token=<youknowit> at the end).
E.g. using Angular.js
<img ng-src="{{contact.link[1].href + tokenForImages}}" alt="photo" />
With regard to CORS in general, there seem to be quite a few places where accessing the API from JS is not working as expected.
Hope this helps.
Not able to comment yet, hence this answer…
Obviously you have already set up the proper client ID and JavaScript origins in the Google developers console.
It seems that the domain shared contacts API does not work as advertised and only abides by its CORS promise when you request JSONP data (your code indicates that you got your entry data using JSON). For JSON format, the API sets the access-control-allow-origin to * instead of the JavaScript origins you list for your project.
But as of today (2015-06-16), if you try to issue a GET, POST… with a different data type (e.g. atom/xml), the Google API will not set the access-control-allow-origin at all, hence your browser will deny your request to access the data (error 405).
This is clearly a bug, that prevents any programmatic use of the shared contacts API but for simple listing of entries: one can no longer create, update, delete entries nor access photos.
Please correct me if I'm mistaken (I wish I am); please comment or edit if you know the best way to file this bug with Google.
Note, for the sake of completeness, here's the code skeleton I use to access contacts (requires jQuery).
<button id="authorize-button" style="visibility: hidden">Authorize</button>
<script type="text/javascript">
var clientId = 'TAKE-THIS-FROM-CONSOLE.apps.googleusercontent.com',
apiKey = 'TAKE-THAT-FROM-GOOGLE-DEVELOPPERS-CONSOLE',
scopes = 'https://www.google.com/m8/feeds';
// Use a button to handle authentication the first time.
function handleClientLoad () {
gapi.client.setApiKey ( apiKey );
window.setTimeout ( checkAuth, 1 );
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
function handleAuthResult ( authResult ) {
var authorizeButton = document.getElementById ( 'authorize-button' );
if ( authResult && !authResult.error ) {
authorizeButton.style.visibility = 'hidden';
var cif = {
method: 'GET',
url: 'https://www.google.com/m8/feeds/contacts/mydomain.com/full/',
data: {
"access_token": authResult.access_token,
"alt": "json",
"max-results": "10"
},
headers: {
"Gdata-Version": "3.0"
},
xhrFields: {
withCredentials: true
},
dataType: "jsonp"
};
$.ajax ( cif ).done ( function ( result ) {
$ ( '#gcontacts' ).html ( JSON.stringify ( result, null, 3 ) );
} );
} else {
authorizeButton.style.visibility = '';
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick ( event ) {
gapi.auth.authorize ( { client_id: clientId, scope: scopes, immediate: false }, handleAuthResult );
return false;
}
</script>
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
<pre id="gcontacts"></pre>
If you replace cif.data.alt by atom and/or cif.dataType by xml, you get the infamous error 405.
ps: cif is of course related to ajax ;-)

Passing authorization bearer token using BreezeJS OData data service

How do I tell Breeze to include an authorization bearer token header when using the OData data service?
//Configured breeze to use OData
breeze.config.initializeAdapterInstance('dataService', 'OData');
//Configured breeze to use AngularJS ajax
var instance = breeze.config.initializeAdapterInstance('ajax', 'angular', true);
//Tried passing authorization bearer token header using setHttp with no success
//NOTE: $http setup with $http.defaults.headers.common['Authorization'] = 'Bearer...'
instance.setHttp($http);
//Tried passing authorization bearer token header using ajax settings with no success
instance.defaultSettings = {
headers: {
'Authorization': 'Bearer...'
},
};
//Fiddler shows no authorization bearer token header for following query
var manager = new breeze.EntityManager('/odata/');
var query = breeze.EntityQuery.from('Customers');
return manager.executeQuery(query).to$q(querySucceeded, queryFailed);
I don't know if you solved your problem. This worked for me:
function configureBreeze() {
// configure to use the model library for Angular
breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);
var accessToken = Security.user.access_token;
if (Security.user.access_token) {
// get the current default Breeze AJAX adapter & add header required for the Web API bearer token mechanism
var ajaxAdapter = breeze.config.getAdapterInstance("ajax");
ajaxAdapter.defaultSettings = {
headers: {
'Authorization': 'Bearer ' + accessToken
},
};
}
}
It is a modification of the configureBreeze method found in the datacontext.js script of the Angular/Breeze SPA template for asp.net MVC4.
Hope it helps.
I had the same problem. After looking at breeze dataservice for oData i think that it just ignores ajax provider cause it's using datajs to do requests. So instance.setHttp($http); won't work. I ended up overriding default request method in datajs like that:
var base = window.OData.request;
window.OData.request = function (request, success, error, handler, httpClient, metadata) {
angular.extend(request.headers, { Authorization: $rootScope.token });
return base(request, success, error, handler, httpClient, metadata);
};
There's an sample on the Breeze Website (under OData AJAX): http://www.getbreezenow.com/documentation/controlling-ajax
var oldClient = OData.defaultHttpClient;
var myClient = {
request: function (request, success, error) {
request.headers.Authorization = authorization;
return oldClient.request(request, success, error);
}
};
OData.defaultHttpClient = myClient;
//instance.defaultSettings = {
// headers: {
// 'Authorization': 'Bearer...'
// },
//};
instance.headers['Authorization'] = 'Bearer...';

Resources