Add Proxy Authentication. Fix #2.

This commit is contained in:
FelisCatus 2014-12-11 18:43:46 +08:00
parent 777ad1c808
commit bae39a247c
12 changed files with 214 additions and 5 deletions

View File

@ -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"
},

View File

@ -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": "真的要删除这个规则吗?"
},

View File

@ -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": "真的要刪除這個規則嗎?"
},

View File

@ -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": "真的要刪除這個規則嗎?"
},

View File

@ -27,6 +27,8 @@
"tabs",
"alarms",
"storage",
"webRequest",
"webRequestBlocking",
"http://*/*",
"https://*/*",
"ftp://*/*",

View File

@ -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

View File

@ -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: ['<all_urls>']}
['blocking']
)
chrome.webRequest.onCompleted.addListener(
@_requestDone.bind(this)
{urls: ['<all_urls>']}
)
chrome.webRequest.onErrorOccurred.addListener(
@_requestDone.bind(this)
{urls: ['<all_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]

View File

@ -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();

View File

@ -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] = {}

View File

@ -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}}

View File

@ -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}')

View File

@ -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