From bae39a247cf765fae574172794c63c50bcd6ef13 Mon Sep 17 00:00:00 2001 From: FelisCatus Date: Thu, 11 Dec 2014 18:43:46 +0800 Subject: [PATCH] Add Proxy Authentication. Fix #2. --- omega-i18n/en/messages.json | 15 ++++ omega-i18n/zh_CN/messages.json | 15 ++++ omega-i18n/zh_HK/messages.json | 15 ++++ omega-i18n/zh_TW/messages.json | 15 ++++ .../overlay/manifest.json | 2 + .../src/options.coffee | 6 +- .../src/proxy_auth.coffee | 78 +++++++++++++++++++ omega-web/src/less/options.less | 9 +++ .../omega/controllers/fixed_profile.coffee | 26 ++++++- omega-web/src/partials/fixed_auth_edit.jade | 26 +++++++ omega-web/src/partials/input_group_clear.jade | 4 +- omega-web/src/partials/profile_fixed.jade | 8 +- 12 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 omega-target-chromium-extension/src/proxy_auth.coffee create mode 100644 omega-web/src/partials/fixed_auth_edit.jade diff --git a/omega-i18n/en/messages.json b/omega-i18n/en/messages.json index 7a4fd21..309f25a 100644 --- a/omega-i18n/en/messages.json +++ b/omega-i18n/en/messages.json @@ -295,6 +295,9 @@ "options_proxy_port": { "message": "Port" }, + "options_proxy_auth": { + "message": "Authentication" + }, "options_scheme_default": { "message": "(default)" }, @@ -541,6 +544,18 @@ "message": "SwitchyOmega Popup", "description": "The page title of the popup. Normally you won't see it." }, + "options_modalHeader_proxyAuth": { + "message": "Proxy Authentication" + }, + "options_proxyAuthUsername": { + "message": "Username" + }, + "options_proxyAuthPassword": { + "message": "Password" + }, + "options_proxyAuthNone": { + "message": "No Authentication" + }, "options_modalHeader_deleteRule": { "message": "Delete Rule" }, diff --git a/omega-i18n/zh_CN/messages.json b/omega-i18n/zh_CN/messages.json index d7657b8..bcbd32b 100644 --- a/omega-i18n/zh_CN/messages.json +++ b/omega-i18n/zh_CN/messages.json @@ -295,6 +295,9 @@ "options_proxy_port": { "message": "代理端口" }, + "options_proxy_auth": { + "message": "代理登录" + }, "options_scheme_default": { "message": "(默认)" }, @@ -544,6 +547,18 @@ "options_modalHeader_deleteRule": { "message": "删除规则" }, + "options_modalHeader_proxyAuth": { + "message": "代理登录" + }, + "options_proxyAuthUsername": { + "message": "用户名" + }, + "options_proxyAuthPassword": { + "message": "密码" + }, + "options_proxyAuthNone": { + "message": "(无密码)" + }, "options_deleteRuleConfirm": { "message": "真的要删除这个规则吗?" }, diff --git a/omega-i18n/zh_HK/messages.json b/omega-i18n/zh_HK/messages.json index db4e739..03a3f8a 100644 --- a/omega-i18n/zh_HK/messages.json +++ b/omega-i18n/zh_HK/messages.json @@ -295,6 +295,9 @@ "options_proxy_port": { "message": "代理端口" }, + "options_proxy_auth": { + "message": "代理認證" + }, "options_scheme_default": { "message": "(默認)" }, @@ -544,6 +547,18 @@ "options_modalHeader_deleteRule": { "message": "刪除規則" }, + "options_modalHeader_proxyAuth": { + "message": "代理認證" + }, + "options_proxyAuthUsername": { + "message": "用戶名" + }, + "options_proxyAuthPassword": { + "message": "密碼" + }, + "options_proxyAuthNone": { + "message": "(无密碼)" + }, "options_deleteRuleConfirm": { "message": "真的要刪除這個規則嗎?" }, diff --git a/omega-i18n/zh_TW/messages.json b/omega-i18n/zh_TW/messages.json index 97d5071..d91d25e 100644 --- a/omega-i18n/zh_TW/messages.json +++ b/omega-i18n/zh_TW/messages.json @@ -295,6 +295,9 @@ "options_proxy_port": { "message": "代理埠" }, + "options_proxy_auth": { + "message": "代理認證" + }, "options_scheme_default": { "message": "(默認)" }, @@ -544,6 +547,18 @@ "options_modalHeader_deleteRule": { "message": "刪除規則" }, + "options_modalHeader_proxyAuth": { + "message": "代理認證" + }, + "options_proxyAuthUsername": { + "message": "用戶名" + }, + "options_proxyAuthPassword": { + "message": "密碼" + }, + "options_proxyAuthNone": { + "message": "(无密碼)" + }, "options_deleteRuleConfirm": { "message": "真的要刪除這個規則嗎?" }, diff --git a/omega-target-chromium-extension/overlay/manifest.json b/omega-target-chromium-extension/overlay/manifest.json index fe07bf0..cf53c1c 100644 --- a/omega-target-chromium-extension/overlay/manifest.json +++ b/omega-target-chromium-extension/overlay/manifest.json @@ -27,6 +27,8 @@ "tabs", "alarms", "storage", + "webRequest", + "webRequestBlocking", "http://*/*", "https://*/*", "ftp://*/*", diff --git a/omega-target-chromium-extension/src/options.coffee b/omega-target-chromium-extension/src/options.coffee index 18f22d2..9650675 100644 --- a/omega-target-chromium-extension/src/options.coffee +++ b/omega-target-chromium-extension/src/options.coffee @@ -6,6 +6,7 @@ url = require('url') chromeApiPromisifyAll = require('./chrome_api') proxySettings = chromeApiPromisifyAll(chrome.proxy.settings) parseExternalProfile = require('./parse_external_profile') +ProxyAuth = require('./proxy_auth') class ChromeOptions extends OmegaTarget.Options parseExternalProfile: (details) -> @@ -149,7 +150,10 @@ class ChromeOptions extends OmegaTarget.Options config['pacScript'].data = prefix + script return setPacScript ?= Promise.resolve() - setPacScript.then(-> + setPacScript.then(=> + @_proxyAuth ?= new ProxyAuth(this) + @_proxyAuth.listen() + @_proxyAuth.setProxies(@_watchingProfiles) proxySettings.setAsync({value: config}) ).then => chrome.proxy.settings.get {}, @_proxyChangeListener diff --git a/omega-target-chromium-extension/src/proxy_auth.coffee b/omega-target-chromium-extension/src/proxy_auth.coffee new file mode 100644 index 0000000..e1e0a30 --- /dev/null +++ b/omega-target-chromium-extension/src/proxy_auth.coffee @@ -0,0 +1,78 @@ +OmegaTarget = require('omega-target') +OmegaPac = OmegaTarget.OmegaPac +Promise = OmegaTarget.Promise + +module.exports = class ProxyAuth + constructor: (options) -> + @options = options + + listening: false + listen: -> + return if @listening + if not chrome.webRequest + @options.log.error('Proxy auth disabled! No webRequest permission.') + return + chrome.webRequest.onAuthRequired.addListener( + @authHandler.bind(this) + {urls: ['']} + ['blocking'] + ) + chrome.webRequest.onCompleted.addListener( + @_requestDone.bind(this) + {urls: ['']} + ) + chrome.webRequest.onErrorOccurred.addListener( + @_requestDone.bind(this) + {urls: ['']} + ) + @listening = true + + _keyForProxy: (proxy) -> "#{proxy.host}:#{proxy.port}" + setProxies: (profiles) -> + @_proxies = {} + processProfile = (profile) => + profile = @options.profile(profile) + return unless profile?.auth + for scheme in OmegaPac.Profiles.schemes when profile[scheme.prop] + auth = profile.auth?[scheme.prop] + continue unless auth + proxy = profile[scheme.prop] + key = @_keyForProxy(proxy) + list = @_proxies[key] + if not list? + @_proxies[key] = list = [] + list.push({ + config: proxy + auth: auth + name: profile.name + '.' + scheme.prop + }) + + if Array.isArray(profiles) + for profile in profiles + processProfile(profile) + else + for _, profile of profiles + processProfile(profile) + + _proxies: {} + _requests: {} + authHandler: (details) -> + return {} unless details.isProxy + req = @_requests[details.requestId] + if not req? + @_requests[details.requestId] = req = {authTries: 0} + + key = @_keyForProxy( + host: details.challenger.host + port: details.challenger.port + ) + + proxy = @_proxies[key]?[req.authTries] + @options.log.log('ProxyAuth', key, req.authTries, proxy?.name) + + return {} unless proxy? + req.authTries++ + return authCredentials: proxy.auth + + _requestDone: (details) -> + delete @_requests[details.requestId] diff --git a/omega-web/src/less/options.less b/omega-web/src/less/options.less index 45a462a..dd66ba6 100644 --- a/omega-web/src/less/options.less +++ b/omega-web/src/less/options.less @@ -403,6 +403,15 @@ main { } } +.proxy-actions { + text-align: center; + padding: 0 !important; +} + +.proxy-auth-toggle { + padding: 5px 7px !important; +} + .host-levels-details { input { .width-initial(); diff --git a/omega-web/src/omega/controllers/fixed_profile.coffee b/omega-web/src/omega/controllers/fixed_profile.coffee index 371b03f..17ba6dd 100644 --- a/omega-web/src/omega/controllers/fixed_profile.coffee +++ b/omega-web/src/omega/controllers/fixed_profile.coffee @@ -1,4 +1,4 @@ -angular.module('omega').controller 'FixedProfileCtrl', ($scope) -> +angular.module('omega').controller 'FixedProfileCtrl', ($scope, $modal) -> $scope.urlSchemes = ['', 'http', 'https', 'ftp'] $scope.urlSchemeDefault = 'fallbackProxy' proxyProperties = @@ -22,10 +22,34 @@ angular.module('omega').controller 'FixedProfileCtrl', ($scope) -> $scope.proxyEditors = {} + $scope.authSupported = {"http": true, "https": true} + $scope.isProxyAuthActive = (scheme) -> + return $scope.profile.auth?[proxyProperties[scheme]]? + $scope.editProxyAuth = (scheme) -> + prop = proxyProperties[scheme] + proxy = $scope.profile[prop] + scope = $scope.$new('isolate') + scope.proxy = proxy + auth = $scope.profile.auth?[prop] + scope.auth = auth && angular.copy(auth) + $modal.open( + templateUrl: 'partials/fixed_auth_edit.html' + scope: scope + size: 'sm' + ).result.then (auth) -> + if not auth?.username + if $scope.profile.auth + $scope.profile.auth[prop] = undefined + else + $scope.profile.auth ?= {} + $scope.profile.auth[prop] = auth + onProxyChange = (proxyEditors, oldProxyEditors) -> return unless proxyEditors for scheme in $scope.urlSchemes proxy = proxyEditors[scheme] + if $scope.profile.auth and not $scope.authSupported[proxy.scheme] + delete $scope.profile.auth[proxyProperties[scheme]] if not proxy.scheme if not scheme proxyEditors[scheme] = {} diff --git a/omega-web/src/partials/fixed_auth_edit.jade b/omega-web/src/partials/fixed_auth_edit.jade new file mode 100644 index 0000000..a525f28 --- /dev/null +++ b/omega-web/src/partials/fixed_auth_edit.jade @@ -0,0 +1,26 @@ +form(ng-submit='authForm.$valid && $close(auth)' name='authForm') + .modal-header + button.close(type='button' ng-click='$dismiss()') + span(aria-hidden='true') × + span.sr-only Close + h4.modal-title {{'options_modalHeader_newProfile' | tr}} + .modal-body(style='padding-bottom: 0;') + .form-group + label.sr-only {{'options_proxyAuthUsername' | tr}} + div(input-group-clear type='text' model='auth.username' autofocus + placeholder='{{"options_proxyAuthUsername" | tr}}') + .form-group(ng-class='{"has-error": !authForm.password.$valid}') + label.sr-only {{'options_proxyAuthPassword' | tr}} + .input-group + input.form-control(type='text' name='password' ng-model='auth.password' + ng-attr-type='{{showPassword ? "text" : "password"}}' + placeholder='{{"options_proxyAuthPassword" | tr}}' ng-show='!!auth.username') + input.form-control(type='text' value='' placeholder='{{"options_proxyAuthNone" | tr}}' + disabled ng-show='!auth.username') + span.input-group-btn + button.btn.btn-default(type='button' ng-click='showPassword = !showPassword' + title="{{'showPassword_' + (showPassword ? 'hide' : 'show') | tr}}" ng-disabled='!auth.username') + span.glyphicon(ng-class='{"glyphicon-eye-close": !showPassword, "glyphicon-eye-open": !!showPassword}') + .modal-footer + button.btn.btn-default(type='button' ng-click='$dismiss()') {{'dialog_cancel' | tr}} + button.btn.btn-primary(type='submit' ng-disabled='!authForm.$valid') {{'dialog_save' | tr}} diff --git a/omega-web/src/partials/input_group_clear.jade b/omega-web/src/partials/input_group_clear.jade index ebaeeae..c2893a9 100644 --- a/omega-web/src/partials/input_group_clear.jade +++ b/omega-web/src/partials/input_group_clear.jade @@ -2,6 +2,6 @@ input.form-control(ng-model='model' ng-attr-type='{{type}}' ng-pattern='ngPattern || catchAll' placeholder='{{placeholder}}' ng-change='modelChange()') span.input-group-btn - button.btn.btn-default.input-group-clear-btn(ng-click='toggleClear()' ng-disabled='!model && !oldModel' - title="{{'inputClear_' + (oldModel ? 'restore' : 'clear') | tr}}") + button.btn.btn-default.input-group-clear-btn(type='button' ng-click='toggleClear()' + ng-disabled='!model && !oldModel' title="{{'inputClear_' + (oldModel ? 'restore' : 'clear') | tr}}") span.glyphicon(ng-class='{"glyphicon-remove": !oldModel, "glyphicon-repeat": !!oldModel}') diff --git a/omega-web/src/partials/profile_fixed.jade b/omega-web/src/partials/profile_fixed.jade index 983442a..1a9d040 100644 --- a/omega-web/src/partials/profile_fixed.jade +++ b/omega-web/src/partials/profile_fixed.jade @@ -9,6 +9,7 @@ div(ng-controller='FixedProfileCtrl') th {{'options_proxy_protocol' | tr}} th {{'options_proxy_server' | tr}} th {{'options_proxy_port' | tr}} + th tbody tr(ng-repeat='scheme in urlSchemes' ng-show='scheme == "" || showAdvanced') td {{schemeDisp[scheme] || ('options_scheme_default' | tr)}} @@ -29,9 +30,14 @@ div(ng-controller='FixedProfileCtrl') input.form-control(type='number' min='1' ng-model='proxyEditors[scheme].port' required) td(ng-if='!proxyEditors[scheme].scheme') input.form-control(type='number' value='' placeholder='{{proxyEditors[""].port}}' disabled) + td.proxy-actions + button.btn.btn-xs.proxy-auth-toggle(ng-show='authSupported[proxyEditors[scheme].scheme]' + ng-class='isProxyAuthActive(scheme) ? "btn-success" : "btn-default"' + type='button' role='button' ng-click='editProxyAuth(scheme)' title='{{"options_proxy_auth" | tr}}') + span.glyphicon.glyphicon-lock tbody(ng-show='!showAdvanced') tr.fixed-show-advanced - td(colspan='6') + td(colspan='7') button.btn.btn-link(ng-click='showAdvanced = true') | #[span.glyphicon.glyphicon-chevron-down] {{'options_proxy_expand' | tr}} section.settings-group