Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(490)

Side by Side Diff: remoting/tools/me2me_virtual_host.py

Issue 10830225: Don't use auth.json in me2me_virtual_host. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 # Virtual Me2Me implementation. This script runs and manages the processes 6 # Virtual Me2Me implementation. This script runs and manages the processes
7 # required for a Virtual Me2Me desktop, which are: X server, X desktop 7 # required for a Virtual Me2Me desktop, which are: X server, X desktop
8 # session, and Host process. 8 # session, and Host process.
9 # This script is intended to run continuously as a background daemon 9 # This script is intended to run continuously as a background daemon
10 # process, running under an ordinary (non-root) user account. 10 # process, running under an ordinary (non-root) user account.
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
67 FIRST_X_DISPLAY_NUMBER = 20 67 FIRST_X_DISPLAY_NUMBER = 20
68 68
69 X_AUTH_FILE = os.path.expanduser("~/.Xauthority") 69 X_AUTH_FILE = os.path.expanduser("~/.Xauthority")
70 os.environ["XAUTHORITY"] = X_AUTH_FILE 70 os.environ["XAUTHORITY"] = X_AUTH_FILE
71 71
72 72
73 # Globals needed by the atexit cleanup() handler. 73 # Globals needed by the atexit cleanup() handler.
74 g_desktops = [] 74 g_desktops = []
75 g_pidfile = None 75 g_pidfile = None
76 76
77 class Config:
78 def __init__(self, path):
79 self.path = path
80 self.data = {}
81 self.changed = False
82
83 def load(self):
84 try:
85 settings_file = open(self.path, 'r')
86 self.data = json.load(settings_file)
87 self.changed = False
88 settings_file.close()
89 except:
90 return False
91 return True
92
93 def save(self):
94 if not self.changed:
95 return True
96 try:
97 old_umask = os.umask(0066)
98 settings_file = open(self.path, 'w')
99 settings_file.write(json.dumps(self.data, indent=2))
100 settings_file.close()
101 os.umask(old_umask)
102 except:
103 return False
104 self.changed = False
105 return True
106
107 def copy_from(self, config):
108 """ Copy all parameters from |config|.
109 Args:
110 config: Config object to copy from
111 """
112 self.data.update(config.data)
113 self.changed = True
114
115 def get(self, key):
116 return self.data.get(key)
117
118 def __getitem__(self, key):
119 return self.data[key]
120
121 def __setitem__(self, key, value):
122 self.data[key] = value
123 self.changed = True
124
125 def __delitem__(self, key):
126 del self.data[key]
127 self.changed = True
77 128
78 class Authentication: 129 class Authentication:
79 """Manage authentication tokens for Chromoting/xmpp""" 130 """Manage authentication tokens for Chromoting/xmpp"""
80 131
81 def __init__(self, config_file): 132 def __init__(self, config):
82 self.config_file = config_file 133 self.config = config
83 134
84 def generate_tokens(self): 135 def generate_tokens(self):
85 """Prompt for username/password and use them to generate new authentication 136 """Prompt for username/password and use them to generate new authentication
86 tokens. 137 tokens.
87 138
88 Raises: 139 Raises:
89 Exception: Failed to get new authentication tokens. 140 Exception: Failed to get new authentication tokens.
90 """ 141 """
91 print "Email:", 142 print "Email:",
92 self.login = raw_input() 143 self.login = raw_input()
93 password = getpass.getpass("App-specific password: ") 144 password = getpass.getpass("App-specific password: ")
94 145
95 chromoting_auth = gaia_auth.GaiaAuthenticator('chromoting') 146 chromoting_auth = gaia_auth.GaiaAuthenticator('chromoting')
96 self.chromoting_auth_token = chromoting_auth.authenticate(self.login, 147 self.chromoting_auth_token = chromoting_auth.authenticate(self.login,
97 password) 148 password)
98 149
99 xmpp_authenticator = gaia_auth.GaiaAuthenticator('chromiumsync') 150 xmpp_authenticator = gaia_auth.GaiaAuthenticator('chromiumsync')
100 self.xmpp_auth_token = xmpp_authenticator.authenticate(self.login, 151 self.xmpp_auth_token = xmpp_authenticator.authenticate(self.login,
101 password) 152 password)
102 153
103 def load_config(self): 154 def load_config(self):
104 try: 155 try:
105 settings_file = open(self.config_file, 'r') 156 self.login = self.config["xmpp_login"]
106 data = json.load(settings_file) 157 self.chromoting_auth_token = self.config["chromoting_auth_token"]
107 settings_file.close() 158 self.xmpp_auth_token = self.config["xmpp_auth_token"]
108 self.login = data["xmpp_login"] 159 except KeyError:
109 self.chromoting_auth_token = data["chromoting_auth_token"]
110 self.xmpp_auth_token = data["xmpp_auth_token"]
111 except:
112 return False 160 return False
113 return True 161 return True
114 162
115 def save_config(self): 163 def save_config(self):
116 data = { 164 self.config["xmpp_login"] = self.login
117 "xmpp_login": self.login, 165 self.config["chromoting_auth_token"] = self.chromoting_auth_token
118 "chromoting_auth_token": self.chromoting_auth_token, 166 self.config["xmpp_auth_token"] = self.xmpp_auth_token
119 "xmpp_auth_token": self.xmpp_auth_token,
120 }
121 # File will contain private keys, so deny read/write access to others.
122 old_umask = os.umask(0066)
123 settings_file = open(self.config_file, 'w')
124 settings_file.write(json.dumps(data, indent=2))
125 settings_file.close()
126 os.umask(old_umask)
127 167
168 def clear_config(self):
169 del self.config["xmpp_login"]
170 del self.config["chromoting_auth_token"]
171 del self.config["xmpp_auth_token"]
128 172
129 class Host: 173 class Host:
130 """This manages the configuration for a host. 174 """This manages the configuration for a host.
131 175
132 Callers should instantiate a Host object (passing in a filename where the 176 Callers should instantiate a Host object (passing in a filename where the
133 config will be kept), then should call either of the methods: 177 config will be kept), then should call either of the methods:
134 178
135 * register(auth): Create a new Host configuration and register it 179 * register(auth): Create a new Host configuration and register it
136 with the Directory Service (the "auth" parameter is used to 180 with the Directory Service (the "auth" parameter is used to
137 authenticate with the Service). 181 authenticate with the Service).
138 * load_config(): Load a config from disk, with details of an existing Host 182 * load_config(): Load a config from disk, with details of an existing Host
139 registration. 183 registration.
140 184
141 After calling register() (or making any config changes) the method 185 After calling register() (or making any config changes) the method
142 save_config() should be called to save the details to disk. 186 save_config() should be called to save the details to disk.
143 """ 187 """
144 188
145 server = 'www.googleapis.com' 189 server = 'www.googleapis.com'
146 url = 'https://' + server + '/chromoting/v1/@me/hosts' 190 url = 'https://' + server + '/chromoting/v1/@me/hosts'
147 191
148 def __init__(self, config_file, auth): 192 def __init__(self, config, auth):
149 """ 193 """
150 Args: 194 Args:
151 config_file: Host configuration file path 195 config: Host configuration object
152 auth: Authentication object with credentials for authenticating with the 196 auth: Authentication object with credentials for authenticating with the
153 Directory service. 197 Directory service.
154 """ 198 """
155 self.config_file = config_file 199 self.config = config
156 self.auth = auth 200 self.auth = auth
157 self.host_id = str(uuid.uuid1()) 201 self.host_id = str(uuid.uuid1())
158 self.host_name = socket.gethostname() 202 self.host_name = socket.gethostname()
159 self.host_secret_hash = None 203 self.host_secret_hash = None
160 self.private_key = None 204 self.private_key = None
161 205
162 def register(self): 206 def register(self):
163 """Generates a private key for the stored |host_id|, and registers it with 207 """Generates a private key for the stored |host_id|, and registers it with
164 the Directory service. 208 the Directory service.
165 209
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
215 def set_pin(self, pin): 259 def set_pin(self, pin):
216 if pin == "": 260 if pin == "":
217 self.host_secret_hash = "plain:" 261 self.host_secret_hash = "plain:"
218 else: 262 else:
219 self.host_secret_hash = "hmac:" + base64.b64encode( 263 self.host_secret_hash = "hmac:" + base64.b64encode(
220 hmac.new(str(self.host_id), pin, hashlib.sha256).digest()) 264 hmac.new(str(self.host_id), pin, hashlib.sha256).digest())
221 265
222 def is_pin_set(self): 266 def is_pin_set(self):
223 return self.host_secret_hash 267 return self.host_secret_hash
224 268
225 def load_config(self): 269 def load_config(self):
Lambros 2012/08/08 22:11:29 Now that load_config no longer pulls data from a f
Sergey Ulanov 2012/08/08 22:41:03 You can look at it as if self.config is just a ref
226 try: 270 try:
227 settings_file = open(self.config_file, 'r') 271 self.host_id = self.config["host_id"]
228 data = json.load(settings_file) 272 self.host_name = self.config["host_name"]
229 settings_file.close() 273 self.host_secret_hash = self.config.get("host_secret_hash")
230 except: 274 self.private_key = self.config["private_key"]
231 logging.info("Failed to load: " + self.config_file) 275 except KeyError:
232 return False 276 return False
233 self.host_id = data["host_id"]
234 self.host_name = data["host_name"]
235 self.host_secret_hash = data.get("host_secret_hash")
236 self.private_key = data["private_key"]
237 return True 277 return True
238 278
239 def save_config(self): 279 def save_config(self):
Lambros 2012/08/08 22:11:29 Same applies as for load_config.
Sergey Ulanov 2012/08/08 22:41:03 Done.
240 data = { 280 self.config["host_id"] = self.host_id
241 "host_id": self.host_id, 281 self.config["host_name"] = self.host_name
242 "host_name": self.host_name, 282 self.config["host_secret_hash"] = self.host_secret_hash
243 "host_secret_hash": self.host_secret_hash, 283 self.config["private_key"] = self.private_key
244 "private_key": self.private_key,
245 }
246 if self.host_secret_hash:
247 data["host_secret_hash"] = self.host_secret_hash
248 284
249 old_umask = os.umask(0066) 285 def clear_config(self):
Lambros 2012/08/08 22:11:29 Similar thing here. clear_config operates on self
Sergey Ulanov 2012/08/08 22:41:03 Moved this function to Config class.
250 settings_file = open(self.config_file, 'w') 286 del self.config["host_id"]
251 settings_file.write(json.dumps(data, indent=2)) 287 del self.config["host_name"]
252 settings_file.close() 288 del self.config["host_secret_hash"]
253 os.umask(old_umask) 289 del self.config["private_key"]
254
255 290
256 class Desktop: 291 class Desktop:
257 """Manage a single virtual desktop""" 292 """Manage a single virtual desktop"""
258 293
259 def __init__(self, sizes): 294 def __init__(self, sizes):
260 self.x_proc = None 295 self.x_proc = None
261 self.session_proc = None 296 self.session_proc = None
262 self.host_proc = None 297 self.host_proc = None
263 self.sizes = sizes 298 self.sizes = sizes
264 g_desktops.append(self) 299 g_desktops.append(self)
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
376 self.session_proc = subprocess.Popen(XSESSION_COMMAND, 411 self.session_proc = subprocess.Popen(XSESSION_COMMAND,
377 stdin=open(os.devnull, "r"), 412 stdin=open(os.devnull, "r"),
378 cwd=HOME_DIR, 413 cwd=HOME_DIR,
379 env=self.child_env) 414 env=self.child_env)
380 if not self.session_proc.pid: 415 if not self.session_proc.pid:
381 raise Exception("Could not start X session") 416 raise Exception("Could not start X session")
382 417
383 def launch_host(self, host): 418 def launch_host(self, host):
384 # Start remoting host 419 # Start remoting host
385 args = [locate_executable(REMOTING_COMMAND), 420 args = [locate_executable(REMOTING_COMMAND),
386 "--host-config=%s" % (host.config_file)] 421 "--host-config=%s" % (host.config.path)]
387 if host.auth.config_file != host.config_file:
388 args.append("--auth-config=%s" % (host.auth.config_file))
389 self.host_proc = subprocess.Popen(args, env=self.child_env) 422 self.host_proc = subprocess.Popen(args, env=self.child_env)
390 logging.info(args) 423 logging.info(args)
391 if not self.host_proc.pid: 424 if not self.host_proc.pid:
392 raise Exception("Could not start remoting host") 425 raise Exception("Could not start remoting host")
393 426
394 427
395 class PidFile: 428 class PidFile:
396 """Class to allow creating and deleting a file which holds the PID of the 429 """Class to allow creating and deleting a file which holds the PID of the
397 running process. This is used to detect if a process is already running, and 430 running process. This is used to detect if a process is already running, and
398 inform the user of the PID. On process termination, the PID file is 431 inform the user of the PID. On process termination, the PID file is
(...skipping 296 matching lines...) Expand 10 before | Expand all | Expand 10 after
695 728
696 atexit.register(cleanup) 729 atexit.register(cleanup)
697 730
698 for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]: 731 for s in [signal.SIGHUP, signal.SIGINT, signal.SIGTERM, signal.SIGUSR1]:
699 signal.signal(s, signal_handler) 732 signal.signal(s, signal_handler)
700 733
701 # Ensure full path to config directory exists. 734 # Ensure full path to config directory exists.
702 if not os.path.exists(CONFIG_DIR): 735 if not os.path.exists(CONFIG_DIR):
703 os.makedirs(CONFIG_DIR, mode=0700) 736 os.makedirs(CONFIG_DIR, mode=0700)
704 737
705 host_config_file = os.path.join(CONFIG_DIR, "host#%s.json" % host_hash) 738 host_config = Config(os.path.join(CONFIG_DIR, "host#%s.json" % host_hash))
739 host_config.load()
706 740
707 # --silent option is specified when we are started from WebApp UI. Don't use 741 auth = Authentication(host_config)
708 # separate auth file in that case. 742 auth_config_loaded = auth.load_config()
709 # TODO(sergeyu): Always use host config for auth parameters. 743 if not auth_config_loaded and not options.silent:
710 if options.silent: 744 # If we failed to load authentication parameters from the host config
711 auth_config_file = host_config_file 745 # then try loading them from the legacy auth.json file.
712 else: 746 auth_config = Config(os.path.join(CONFIG_DIR, "auth.json"))
713 auth_config_file = os.path.join(CONFIG_DIR, "auth.json") 747 if auth_config.load():
748 host_config.copy_from(auth_config)
749 auth_config_loaded = auth.load_config()
714 750
715 auth = Authentication(auth_config_file) 751 host = Host(host_config, auth)
716 auth_config_loaded = auth.load_config()
717
718 host = Host(host_config_file, auth)
719 host_config_loaded = host.load_config() 752 host_config_loaded = host.load_config()
720 753
721 if options.silent: 754 if options.silent:
722 if not host_config_loaded or not auth_config_loaded: 755 if not host_config_loaded or not auth_config_loaded:
723 logging.error("Failed to load host configuration.") 756 logging.error("Failed to load host configuration.")
724 return 1 757 return 1
725 else: 758 else:
726 need_auth_tokens = not auth_config_loaded 759 need_auth_tokens = not auth_config_loaded
727 need_register_host = not host_config_loaded 760 need_register_host = not host_config_loaded
728 # Outside the loop so user doesn't get asked twice. 761 # Outside the loop so user doesn't get asked twice.
729 if need_register_host: 762 if need_register_host:
730 host.ask_pin() 763 host.ask_pin()
731 elif options.new_pin or not host.is_pin_set(): 764 elif options.new_pin or not host.is_pin_set():
732 host.ask_pin() 765 host.ask_pin()
733 host.save_config() 766 host.save_config()
734 running, pid = PidFile(pid_filename).check() 767 running, pid = PidFile(pid_filename).check()
735 if running and pid != 0: 768 if running and pid != 0:
736 os.kill(pid, signal.SIGUSR1) 769 os.kill(pid, signal.SIGUSR1)
737 print "The running instance has been updated with the new PIN." 770 print "The running instance has been updated with the new PIN."
738 return 0 771 return 0
739 772
740 # The loop is to deal with the case of registering a new Host with 773 # The loop is to deal with the case of registering a new Host with
741 # previously-saved auth tokens (from a previous run of this script), which 774 # previously-saved auth tokens (from a previous run of this script), which
742 # may require re-prompting for username & password. 775 # may require re-prompting for username & password.
743 while True: 776 while True:
744 try: 777 if need_auth_tokens:
745 if need_auth_tokens: 778 try:
746 auth.generate_tokens() 779 auth.generate_tokens()
747 auth.save_config() 780 auth.save_config()
Lambros 2012/08/08 22:11:29 Since save_config() no longer writes to disk, this
Sergey Ulanov 2012/08/08 22:41:03 Done.
748 need_auth_tokens = False 781 need_auth_tokens = False
749 except Exception: 782 except Exception:
750 logging.error("Authentication failed") 783 logging.error("Authentication failed")
751 return 1 784 return 1
752 785
753 try: 786 if need_register_host:
754 if need_register_host: 787 try:
755 host.register() 788 host.register()
756 host.save_config() 789 host.save_config()
757 except urllib2.HTTPError, err: 790 except urllib2.HTTPError, err:
758 if err.getcode() == 401: 791 if err.getcode() == 401:
759 # Authentication failed - re-prompt for username & password. 792 # Authentication failed - re-prompt for username & password.
760 need_auth_tokens = True 793 need_auth_tokens = True
761 continue 794 continue
762 else: 795 else:
763 # Not an authentication error. 796 # Not an authentication error.
764 logging.error("Directory returned error: " + str(err)) 797 logging.error("Directory returned error: " + str(err))
765 logging.error(err.read()) 798 logging.error(err.read())
766 return 1 799 return 1
767 800
768 # |auth| and |host| are both set up, so break out of the loop. 801 # |auth| and |host| are both set up, so break out of the loop.
769 break 802 break
770 803
804 if not host_config.save():
805 logging.error("Faled to save host configuration.")
806 return 1
807
771 global g_pidfile 808 global g_pidfile
772 g_pidfile = PidFile(pid_filename) 809 g_pidfile = PidFile(pid_filename)
773 running, pid = g_pidfile.check() 810 running, pid = g_pidfile.check()
774 811
775 if running: 812 if running:
776 print "An instance of this script is already running." 813 print "An instance of this script is already running."
777 print "Use the -k flag to terminate the running instance." 814 print "Use the -k flag to terminate the running instance."
778 print "If this isn't the case, delete '%s' and try again." % pid_filename 815 print "If this isn't the case, delete '%s' and try again." % pid_filename
779 return 1 816 return 1
780 817
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
859 logging.info("Host process terminated") 896 logging.info("Host process terminated")
860 desktop.host_proc = None 897 desktop.host_proc = None
861 898
862 # These exit-codes must match the ones used by the host. 899 # These exit-codes must match the ones used by the host.
863 # See remoting/host/constants.h. 900 # See remoting/host/constants.h.
864 # Delete the host or auth configuration depending on the returned error 901 # Delete the host or auth configuration depending on the returned error
865 # code, so the next time this script is run, a new configuration 902 # code, so the next time this script is run, a new configuration
866 # will be created and registered. 903 # will be created and registered.
867 if os.WEXITSTATUS(status) == 2: 904 if os.WEXITSTATUS(status) == 2:
868 logging.info("Host configuration is invalid - exiting.") 905 logging.info("Host configuration is invalid - exiting.")
869 try: 906 auth.clear_config()
870 os.remove(host.config_file) 907 host.clear_config()
871 os.remove(auth.config_file) 908 host_config.save()
872 except:
873 pass
874 return 0 909 return 0
875 elif os.WEXITSTATUS(status) == 3: 910 elif os.WEXITSTATUS(status) == 3:
876 logging.info("Host ID has been deleted - exiting.") 911 logging.info("Host ID has been deleted - exiting.")
877 try: 912 host.clear_config()
878 os.remove(host.config_file) 913 host_config.save()
879 except:
880 pass
881 return 0 914 return 0
882 elif os.WEXITSTATUS(status) == 4: 915 elif os.WEXITSTATUS(status) == 4:
883 logging.info("OAuth credentials are invalid - exiting.") 916 logging.info("OAuth credentials are invalid - exiting.")
884 try: 917 auth.clear_config()
885 os.remove(auth.config_file) 918 host_config.save()
886 except:
887 pass
888 return 0 919 return 0
889 elif os.WEXITSTATUS(status) == 5: 920 elif os.WEXITSTATUS(status) == 5:
890 logging.info("Host domain is blocked by policy - exiting.") 921 logging.info("Host domain is blocked by policy - exiting.")
891 os.remove(host.config_file) 922 os.remove(host.config_file)
892 return 0 923 return 0
893 924
894 if __name__ == "__main__": 925 if __name__ == "__main__":
895 logging.basicConfig(level=logging.DEBUG) 926 logging.basicConfig(level=logging.DEBUG)
896 sys.exit(main()) 927 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698