Index: gclient.py |
diff --git a/gclient.py b/gclient.py |
index 3007487a5930d0f09aeac1260defe3be12bd4360..bbe6d582b8aaa12e412e8ce3a9eb51288739e0dc 100755 |
--- a/gclient.py |
+++ b/gclient.py |
@@ -342,6 +342,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): |
# A cache of the files affected by the current operation, necessary for |
# hooks. |
self._file_list = [] |
+ # List of host names from which dependencies are allowed. |
+ # Default is an empty set, meaning unspecified in DEPS file, and hence all |
+ # hosts will be allowed. Non-empty set means whitelist of hosts. |
+ # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive. |
+ self._allowed_hosts = frozenset() |
# If it is not set to True, the dependency wasn't processed for its child |
# dependency, i.e. its DEPS wasn't read. |
self._deps_parsed = False |
@@ -687,6 +692,18 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): |
rel_deps.add(os.path.normpath(os.path.join(self.name, d))) |
self.recursedeps = rel_deps |
+ if 'allowed_hosts' in local_scope: |
+ try: |
+ self._allowed_hosts = frozenset(local_scope.get('allowed_hosts')) |
+ except TypeError: # raised if non-iterable |
+ pass |
+ if not self._allowed_hosts: |
+ logging.warning("allowed_hosts is specified but empty %s", |
+ self._allowed_hosts) |
+ raise gclient_utils.Error( |
+ 'ParseDepsFile(%s): allowed_hosts must be absent ' |
+ 'or a non-empty iterable' % self.name) |
+ |
# Convert the deps into real Dependency. |
deps_to_add = [] |
for name, url in deps.iteritems(): |
@@ -756,6 +773,21 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): |
print('Using parent\'s revision date %s since we are in a ' |
'different repository.' % options.revision) |
+ def findDepsFromNotAllowedHosts(self): |
+ """Returns a list of depenecies from not allowed hosts. |
+ |
+ If allowed_hosts is not set, allows all hosts and returns empty list. |
+ """ |
+ if not self._allowed_hosts: |
+ return [] |
+ bad_deps = [] |
+ for dep in self._dependencies: |
+ if isinstance(dep.url, basestring): |
+ parsed_url = urlparse.urlparse(dep.url) |
+ if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts: |
+ bad_deps.append(dep) |
+ return bad_deps |
+ |
# Arguments number differs from overridden method |
# pylint: disable=W0221 |
def run(self, revision_overrides, command, args, work_queue, options): |
@@ -1053,6 +1085,11 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): |
@property |
@gclient_utils.lockedmethod |
+ def allowed_hosts(self): |
+ return self._allowed_hosts |
+ |
+ @property |
+ @gclient_utils.lockedmethod |
def file_list(self): |
return tuple(self._file_list) |
@@ -1077,7 +1114,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): |
out = [] |
for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps', |
'custom_vars', 'deps_hooks', 'file_list', 'should_process', |
- 'processed', 'hooks_ran', 'deps_parsed', 'requirements'): |
+ 'processed', 'hooks_ran', 'deps_parsed', 'requirements', |
+ 'allowed_hosts'): |
# First try the native property if it exists. |
if hasattr(self, '_' + i): |
value = getattr(self, '_' + i, False) |
@@ -2086,6 +2124,27 @@ def CMDhookinfo(parser, args): |
return 0 |
+def CMDverify(parser, args): |
+ """Verifies the DEPS file deps are only from allowed_hosts.""" |
+ (options, args) = parser.parse_args(args) |
+ client = GClient.LoadCurrentConfig(options) |
+ if not client: |
+ raise gclient_utils.Error('client not configured; see \'gclient config\'') |
+ client.RunOnDeps(None, []) |
+ # Look at each first-level dependency of this gclient only. |
+ for dep in client.dependencies: |
+ bad_deps = dep.findDepsFromNotAllowedHosts() |
+ if not bad_deps: |
+ continue |
+ print "There are deps from not allowed hosts in file %s" % dep.deps_file |
+ for bad_dep in bad_deps: |
+ print "\t%s at %s" % (bad_dep.name, bad_dep.url) |
+ print "allowed_hosts:", ', '.join(dep.allowed_hosts) |
+ sys.stdout.flush() |
+ raise gclient_utils.Error( |
+ 'dependencies from disallowed hosts; check your DEPS file.') |
+ return 0 |
+ |
class OptionParser(optparse.OptionParser): |
gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient') |