Detect denied access to iframe url - javascript

I am trying to check the url of an iframe, but I just realized that I don't need the actual url, just information of whether the url is within my own domain or not.
I figured I could use
document.getElementById("frameid").documentWindow.location.href
and it will return an error if the url is not within my domain. But how do I intercept this error?

The post in the comment I got was helpful, but I did not find the post on my own, so I thought I'd post my final solution here in case somebody else comes here with a similar question.
$('#iFrame').on('load', function () {
try {
// checks if iframe content is internal
test = this.contentWindow.document.location.href;
showIframe = false;
}
catch (err) {
// if an error is thrown, iframe content is external
showIframe = true;
}
if (showIframe) {
// do some stuff
}
}

Related

How to track traffic coming from an iframe with google tag manager?

My website will be used as an iframe on another website. My intention is to create a variable on google tag manager that will detect if the traffic I'm receiving is coming from a normal source or from a website with my iframe on it. I came up with this, but I don't think it could be correct.
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
Create a new "User defined variable" of type "Custom Javascript" with the Code that you have posted.

How can I test a client-side redirect to a 3rd party site with Cypress?

Cypress offers a simple way to test for server-side redirects using request:
cy.request({
url: `/dashboard/`,
followRedirect: false, // turn off following redirects
}).then((resp) => {
expect(resp.redirectedToUrl).to.eq('http://example.com/session/new')
})
However this doesn't work for client-side redirects because the page is loaded successfully before the redirect happens, meaning the response is for the page, not for the redirect.
How can I test a client-side redirect?
I need a way of catching the redirect and verifying that:
it occurred
was to the correct URL.
Note:
I don't want to follow the redirect away from the app that is being tested. I'm not testing the whole auth flow. I just need to know there was a redirect.
I can't change this auth flow. The redirect is unavoidable.
The redirect happens during initialisation, not as a result of any user interaction.
The redirect uses: window.location.href = url
See my answer below for an attempt at resolving this.
Update: This isn't solid. I've just done some refactoring and it seems that even this solution is flawed. It is possible for the redirect to happen in between cypress visiting the page and triggering the cy.wait so the test ends up waiting for something that has already happened. The below might still work for you depending on when your redirect is triggered, but if it's triggered on initialisation, it appears this will not work.
Not really loving this solution as the redirect still happens (I haven't found a way to cancel it), but it at least tests that the redirect happens, and allows me to check the query:
cy.intercept({ pathname: `/sessions/new` }).as(`loginRedirect`)
cy.visit(`/dashboard/`)
cy.location().then(($location) => {
cy.wait(`#loginRedirect`).then(($interceptor) => {
const { query } = urlParse($interceptor.request.url)
expect(query).to.equal(`?token=true&redirect=${$location.href}`)
})
})
Note: route2 changed to intercept in v6.
Gleb Bahmutov has an approach at Deal with window.location.replace, which renames window.location in the source to window.__location which is effectively the stub.
It uses cy.intercept() to modify the loading page before it hits the browser, i.e before window.location is instantiated and becomes a totally immutable/incorruptible object.
it('replaces', () => {
cy.on('window:before:load', (win) => {
win.__location = { // set up the stub
replace: cy.stub().as('replace')
}
})
cy.intercept('GET', 'index.html', (req) => { // catch the page as it loads
req.continue(res => {
res.body = res.body.replaceAll(
'window.location.replace', 'window.__location.replace')
})
}).as('index')
cy.visit('index.html')
cy.wait('#index')
cy.contains('h1', 'First page')
cy.get('#replace').should('have.been.calledOnceWith', 'https://www.cypress.io')
})
This stubs replace, but the same should work for the href setter.
Here is what I have found as the only was to detect a successful redirect client side if it is to a third-party website. JavaScript has a handy-dandy function called window.open(). There are many things that you can do with it including redirecting the webpage. This can be done by setting the target to _self or to _top. By using a while loop, you are running code as quickly as you can. Here is a rough-draft of how I would record a client-side redirect.
var redirectURL = 'https://www.example.com/',
redirectWindow = window.open(redirectURL, '_top'),
redirected = false,
count = 0;
while(true) {
if (redirectWindow.document.readyState === 'complete') {
redirected = true;
//Your code here. I am just logging that it is in the process of redirection.
console.log('redirecting')
break;
}
if (count > 2000) {
redirected = false;
break;
}
count++
}
if (redirected == false) {
console.log('Error: Redirect Failed');
}

Get around same-origin policy to get top URL of a page from inside a cross-domain iframe

I've got some code running in an iframe on 3rd-party sites. Some will be directly in the top page, some will be inside another iframe and some of these may be cross-domain. I need to find a way to get the URL value of the top page using any means necessary.
The furthest I can go up due to cross-domain policy is until the browser stops what the code is doing. I catch the error and look at the referrer of the current window context I'm in. Most cases the page above this is the top page, but not necessarily.
The only way I can see around this is building up a list of URLs which I think are the top page, and then sending a bot with a JS browser validate by seeing if the iframe my code got up to was in fact directly nested in them.
That's still not particularly accurate though, and I'm sure there must be another way of doing it...
Thanks to anyone who can help.
There is actually a way to get the domain in both Chrome and Opera, (in multiple nested cross-domain iframes), though it is not possible in other browsers.
You need to use the 'window.location.ancestorOrigins' property.
I have created a snippet of code below, which should work for you and if you think you can improve the code or comments, please don't hesitate to edit the gist on Github so we can make it even better:
Gist: https://gist.github.com/ocundale/281f98a36a05c183ff3f.js
Code (ES2015):
// return topmost browser window of current window & boolean to say if cross-domain exception occurred
const getClosestTop = () => {
let oFrame = window,
bException = false;
try {
while (oFrame.parent.document !== oFrame.document) {
if (oFrame.parent.document) {
oFrame = oFrame.parent;
} else {
//chrome/ff set exception here
bException = true;
break;
}
}
} catch(e){
// Safari needs try/catch so sets exception here
bException = true;
}
return {
'topFrame': oFrame,
'err': bException
};
};
// get best page URL using info from getClosestTop
const getBestPageUrl = ({err:crossDomainError, topFrame}) => {
let sBestPageUrl = '';
if (!crossDomainError) {
// easy case- we can get top frame location
sBestPageUrl = topFrame.location.href;
} else {
try {
try {
// If friendly iframe
sBestPageUrl = window.top.location.href;
} catch (e) {
//If chrome use ancestor origin array
let aOrigins = window.location.ancestorOrigins;
//Get last origin which is top-domain (chrome only):
sBestPageUrl = aOrigins[aOrigins.length - 1];
}
} catch (e) {
sBestPageUrl = topFrame.document.referrer;
}
}
return sBestPageUrl;
};
// To get page URL, simply run following within an iframe on the page:
const TOPFRAMEOBJ = getClosestTop();
const PAGE_URL = getBestPageUrl(TOPFRAMEOBJ);
If anybody would like the code in standard ES5, let me know, or simply run it through a converter online.
Definitely not possible without communicating with some sort of external system. The cleanest/most accurate way to gather data is to get the top window URL if the browser lets you, but catch errors and use the referer with a flag to note it's the referer.

Catch X-Frame-Options Error in javascript

Is there any way to catch an error when loading an iframe from another domain. Here is an example in jsfiddle. http://jsfiddle.net/2Udzu/ . I need to show a message if I receive an error.
Here is what I would like to do, but it doesn't work:
$('iframe')[0].onerror = function(e) {
alert('There was an error loading the iFrame');
}
Anyone have any ideas?
The onerror is applicable only for script errors. Frame content error checking must be done using any other method. Here's one example.
<script>
function chkFrame(fr) {
if (!fr.contentDocument.location) alert('Cross domain');
}
</script>
<iframe src="http://www.google.com/" onload="chkFrame(this)"></iframe>
Due to cross domain restriction, there's no way to detect whether a page is successfully loaded or if the page can't be loaded due to client errors (HTTP 4xx errors) and server errors (HTTP 5xx errors).
If both the parent site and the iframe-url is accessible by you, a way to know that the page is fully loaded (without "sameorigin" issues) is sending a message (postMessage) from the child to the parent like this;
Parent site (containing the iframe)
//Listen for message
window.addEventListener("message", function(event) {
if (event.data === "loading_success") {
//Yay
}
});
//Check whether message has come through or not
iframe_element.onload = function () {
//iframe loaded...
setTimeout(function() {
if (!iframeLoaded) {
//iframe loaded but no message from the site - URL not allowed
alert("Failure!");
}
}, 500);
};
Child site (URL from the iframe)
parent.postMessage("loading_success", "https://the_origin_site.url/");
You could get the_origin_site.url by using a server-side language like PHP if you want the possibility for multiple origins
The accepted answer only works if the domain you're trying to put in an iframe is the same as the one you're requesting from - this solution works for cross-domain where you have access to the scripts on both domains.
I am using following code to detect whether a x-frame-option error occured or another with jquery
$(iframe).load(function (e) {
try
{
// try access to check
console.log(this.contentWindow.document);
// Access possible ...
}
catch (e)
{
// Could not access. Read out error type
console.log(e);
var messageLC = e.message.toLowerCase();
if (messageLC.indexOf("x-frame-options") > -1 || messageLC.indexOf('blocked a frame with origin') > -1 || messageLC.indexOf('accessing a cross-origin') > -1)
{
// show Error Msg with cause of cross-origin access denied
}
else
{
// Shoe Error Msg with other cause
}
}
});

Catching same origin exception in Javascript?

I'm trying to create my own XMLHttpRequest framework to learn how this things work internally.
A thing that puzzles me is that I cannot find how to catch a "Same origin" exception.
The idea behind this is that I try to load a URL, if I get a Same origin exception, I re-request the URL through a proxy script local for the script. The reason I do this is because I need to access production data from a development sandbox and I want it to be as transparent as possible for the script itself.
I know it's a bad practice but this is the least intrusive way of doing this at the moment :)
Just to clear things - I don't want to bypass same origin, I just want to catch the thrown exception so I can do something about it.
Here is the code I currently use for my xhr:
var net = function (url, cb, setts){
this.url = url;
this.cb = cb;
var oThis = this;
if (!this.xhr) {
this.xhr = new XMLHttpRequest();
this.xhr.onreadystatechange = function() {
if (oThis.xhr.readyState == 4 && oThis.xhr.status == 200) {
document.body.innerHTML += "RS: "+oThis.xhr.readyState+"; ST:"+oThis.xhr.status+"; RP:"+oThis.xhr.responseText+"<br>";
}
else {
// do some other stuff :)
document.body.innerHTML += "RS: "+oThis.xhr.readyState+"; ST:"+oThis.xhr.status+"; RP:"+oThis.xhr.responseText+"<br>";
}
}
}
this.xhr.open("GET", url,true);
this.xhr.send();
} // It's WIP so don't be scared about the unused vars or hardcoded values :)
I've tried to try...catch around xhr.send(); but no avail, still can't catch the exceptions.
Any ideas or pointers would be greatly appreciated.
xhr.onreadystatechange = function() {
if (xhr.readyState==4) {
if (xhr.status==0) {
alert("denied");
} else {
alert("allowed");
}
}
}
Are you sure it's actually supposed to throw an exception? I can't see anything in the specifications: http://www.w3.org/TR/XMLHttpRequest/#exceptions Looks like it does. My bad.
In either case, you can always check the domain of the incoming string against the domain of the page the user is currently on.
FWIW, as you can see by this jsFiddle (open up Web Inspector), Chrome doesn't really throw an exception. It just says "Failed to load resource".

Categories