I noticed that browser extensions have the permission to access localStorage from any webpage (Get localStorage from within extension without loading a page) as well as cookies (Access cookies from Google Chrome extension). If you give them permissions to access all data on any webpage, then their glorified window objects can do this. (can browser extensions do more than that?)
Let's assume you have a script like this:
<script>
function SecretThing(){
// give the client a secret safe from browser extensions: (?)
var mySecret = Crypto.random()
// some cryptography with mySecret
}
var secretThing = new SecretThing();
</script>
Basically, I am wondering if I could do math on mySecret without ever revealing mySecret to a chrome extension. inside of a "SecretThing" object. I would only write getters to get stuff (e.g. signed or encrypted messages) from the secret.
I am not sure a window object could even access mySecret (or can it?), which is why I think that maybe a browser extension (which i said was mainly a window object) might also not be able to. What do you think? I have never made a browser extension before.
Related
I'm working on a CLI with OCLIF. In one of the commands, I need to simulate a couple of clicks on a web page (using the WebdriverIO framework for that). Before you're able to reach the desired page, there is a redirect to a page with a login prompt. When I use WebdriverIO methods related to alerts such as browser.getAlertText(), browser.sendAlertText() or browser.acceptAlert, I always get the error no such alert.
As an alternative, I tried to get the URL when I am on the page that shows the login prompt. With the URL, I wanted to do something like browser.url(https://<username>:<password>#<url>) to circumvent the prompt. However, browser.url() returns chrome-error://chromewebdata/ as URL when I'm on that page. I guess because the focus is on the prompt and that doesn't have an URL. I also don't know the URL before I land on that page. When being redirected, a query string parameter containing a token is added to the URL that I need.
A screenshot of the prompt:
Is it possible to handle this scenario with WebdriverIO? And if so, how?
You are on the right track, probably there are some fine-tunings that you need to address to get it working.
First off, regarding the chrome-error://chromewebdata errors, quoting Chrome DOCs:
If you see errors with a location like chrome-error://chromewebdata/
in the error stack, these errors are not from the extension or from
your app - they are usually a sign that Chrome was not able to load
your app.
When you see these errors, first check whether Chrome was able to load
your app. Does Chrome say "This site can't be reached" or something
similar? You must start your own server to run your app. Double-check
that your server is running, and that the url and port are configured
correctly.
A lot of words that sum up to: Chrome couldn't load the URL you used inside the browser.url() command.
I tried myself on The Internet - Basic Auth page. It worked like a charm.
URL without basic auth credentials:
URL WITH basic auth credentials:
Code used:
it('Bypass HTTP basic auth', () => {
browser.url('https://admin:admin#the-internet.herokuapp.com/basic_auth');
browser.waitForReadyState('complete');
const banner = $('div.example p').getText().trim();
expect(banner).to.equal('Congratulations! You must have the proper credentials.');
});
What I'd do is manually go through each step, trying to emulate the same flow in the script you're using. From history I can tell you, I dealt with some HTTP web-apps that required a refresh after issuing the basic auth browser.url() call.
Another way to tackle this is to make use of some custom browser profiles (Firefox | Chrome) . I know I wrote a tutorial on it somewhere on SO, but I'm too lazy to find it. I reference a similar post here.
Short story, manually complete the basic auth flow (logging in with credentials) in an incognito window (as to isolate the configurations). Open chrome://version/ in another tab of that session and store the contents of the Profile Path. That folder in going to keep all your sessions & preserve cookies and other browser data.
Lastly, in your currentCapabilities, update the browser-specific options to start the sessions with a custom profile, via the '--user-data-dir=/path/to/your/custom/profile. It should look something like this:
'goog:chromeOptions': {
args: [
'--user-data-dir=/Users/iamdanchiv/Desktop/scoped_dir18256_17319',
],
}
Good luck!
I am new to developing Chrome Extensions.
I am trying to get the values for one of the keys which is stored in my browser's localstorage (inspect -> Application -> Localstorage). What I want to do is in my Chrome Extension, when you click a button, I just need it to fetch the value from localstorage for the page which is open in my browser's current tab.
This is the JS function which I am using -
chrome.storage.local.get(['granted.selected'], function(result) {
console.log('Value currently is ' + result.granted.selected);
});
granted.selected - is the name of the key whose value I want to fetch.
When this executes, I get "Value currently is undefined" whereas I want it to fetch the values stored in the above key (granted.selected).
What do I need to add to have this fetch the value for that key from my current open tab's localstorage?
In short - Just want to access a webpage's localStorage from a Chrome extension.
Any help will be greatly appreciated. Thanks!!
chrome.storage.local is not localStorage, it's a completely different storage API.
localStorage is a DOM storage.
To access DOM of a web page you need a content script [1].
Here's an example of programmatic injection in the popup script or in the background script:
chrome.tabs.executeScript({
code: 'localStorage["keyName"]'
}, ([result] = []) => {
if (!chrome.runtime.lastError) {
console.log(result);
// use the result here inside the callback
}
});
Notes:
You'll need permissions in manifest.json as explained in [1].
keyName is the name of the key as seen in devtools "Application" panel of the web page
each part of an extension has its own devtools console.
I read in https://developer.chrome.com/extensions/storage
that it is possible for a content script to read directly from the storage api.
I have a setting page where i allow the user to save credentials, and i want to access those credentials from the content script. for some reason when i "get" from the content-script i get empty values...
Also -
How can i debug and see the Storage data from Chrome Developer tools?
chrome.storage.sync.get({'username':undefined,'password':undefined}, function(items) {
console.log(items.username)
username = items.username;
password = items.password;
console.log(typeof(username))
});
I Found that if i don't use the Dictionary in the .get api it works.
For some reason(probably a bug) when using the dictionary interface from content-script it doesn't find the data.
From the setting page the .get with dictionary worked.
Content Script can be injected programatically or permanently by declaring in Extension manifest file. Programatic injection require host permission, which is generally grant by browser or page action.
In my use case, I want to inject gmail, outlook.com and yahoo mail web site without user action. I can do by declaring all of them manifest, but by doing so require all data access to those account. Some use may want to grant only outlook.com, but not gmail. Programatic injection does not work because I need to know when to inject. Using tabs permission is also require another permission.
Is there any good way to optionally inject web site?
You cannot run code on a site without the appropriate permissions. Fortunately, you can add the host permissions to optional_permissions in the manifest file to declare them optional and still allow the extension to use them.
In response to a user gesture, you can use chrome.permission.request to request additional permissions. This API can only be used in extension pages (background page, popup page, options page, ...). As of Chrome 36.0.1957.0, the required user gesture also carries over from content scripts, so if you want to, you could add a click event listener from a content script and use chrome.runtime.sendMessage to send the request to the background page, which in turn calls chrome.permissions.request.
Optional code execution in tabs
After obtaining the host permissions (optional or mandatory), you have to somehow inject the content script (or CSS style) in the matching pages. There are a few options, in order of my preference:
Use the chrome.declarativeContent.RequestContentScript action to insert a content script in the page. Read the documentation if you want to learn how to use this API.
Use the webNavigation API (e.g. chrome.webNavigation.onCommitted) to detect when the user has navigated to the page, then use chrome.tabs.executeScript to insert the content script in the tab (or chrome.tabs.insertCSS to insert styles).
Use the tabs API (chrome.tabs.onUpdated) to detect that a page might have changed, and insert a content script in the page using chrome.tabs.executeScript.
I strongly recommend option 1, because it was specifically designed for this use case. Note: This API was added in Chrome 38, but only worked with optional permissions since Chrome 39. Despite the "WARNING: This action is still experimental and is not supported on stable builds of Chrome." in the documentation, the API is actually supported on stable. Initially the idea was to wait for a review before publishing the API on stable, but that review never came and so now this API has been working fine for almost two years.
The second and third options are similar. The difference between the two is that using the webNavigation API adds an additional permission warning ("Read your browsing history"). For this warning, you get an API that can efficiently filter the navigations, so the number of chrome.tabs.executeScript calls can be minimized.
If you don't want to put this extra permission warning in your permission dialog, then you could blindly try to inject on every tab. If your extension has the permission, then the injection will succeed. Otherwise, it fails. This doesn't sound very efficient, and it is not... ...on the bright side, this method does not require any additional permissions.
By using either of the latter two methods, your content script must be designed in such a way that it can handle multiple insertions (e.g. with a guard). Inserting in frames is also supported (allFrames:true), but only if your extension is allowed to access the tab's URL (or the frame's URL if frameId is set).
I advise against using declarativeContent APIs because they're deprecated and buggy with CSS, as described by the last comment on https://bugs.chromium.org/p/chromium/issues/detail?id=708115.
Use the new content script registration APIs instead. Here's what you need, in two parts:
Programmatic script injection
There's a new contentScripts.register() API which can programmatically register content scripts and they'll be loaded exactly like content_scripts defined in the manifest:
browser.contentScripts.register({
matches: ['https://your-dynamic-domain.example.com/*'],
js: [{file: 'content.js'}]
});
This API is only available in Firefox but there's a Chrome polyfill you can use. If you're using Manifest v3, there's the native chrome.scripting.registerContentScript which does the same thing but slightly differently.
Acquiring new permissions
By using chrome.permissions.request you can add new domains on which you can inject content scripts. An example would be:
// In a content script or options page
document.querySelector('button').addEventListener('click', () => {
chrome.permissions.request({
origins: ['https://your-dynamic-domain.example.com/*']
}, granted => {
if (granted) {
/* Use contentScripts.register */
}
});
});
And you'll have to add optional_permissions in your manifest.json to allow new origins to be requested:
{
"optional_permissions": [
"*://*/*"
]
}
In Manifest v3 this property was renamed to optional_host_permissions.
I also wrote some tools to further simplify this for you and for the end user, such as
webext-domain-permission-toggle and webext-dynamic-content-scripts. They will automatically register your scripts in the next browser launches and allow the user the remove the new permissions and scripts.
Since the existing answer is now a few years old, optional injection is now much easier and is described here. It says that to inject a new file conditionally, you can use the following code:
// The lines I have commented are in the documentation, but the uncommented
// lines are the important part
//chrome.runtime.onMessage.addListener((message, callback) => {
// if (message == “runContentScript”){
chrome.tabs.executeScript({
file: 'contentScript.js'
});
// }
//});
You will need the Active Tab Permission to do this.
I have javascript code running in the background page of my chrome extension which calls Google api. The code below essentially requests for oAuth2 token.
function init(){
gapi.client.setApiKey(apiKey);
console.log("Initializing...");
gapi.auth.init(function() { setTimeout(authenticate,100)});
}
function authenticate(){
console.log("Initialized. Now AUthenticating");
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false},api);
}
The auth window opens as expected, however after user logs in and permits my app, the page merely freezes. The callback (api()) is never made and I dont receive a token. Is it because I'm calling it from a background page and the auth window has no way of sending a response back?
If yes, what is the workaround/ solution?
If so, how can I fix this?
You can use Boris Smus's OAuth 2.0 Library for Chrome Extension. It works great in my extension.
The reason why you cannot use OAuth2.0 in chrome extension:
Chrome extensions can't directly use the OAuth 2.0 server-side or
client-side flows because they live at chrome-extension:// URLs