def user_ssh_remove_key(username, key): user = _get_user_for_ssh(username, ["homeDirectory", "uid"]) if not user: raise Exception("User with username '%s' doesn't exists" % username) authorized_keys_file = os.path.join( user["homeDirectory"][0], ".ssh", "authorized_keys" ) if not os.path.exists(authorized_keys_file): raise Exception( "this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file) ) authorized_keys_content = read_file(authorized_keys_file) if key not in authorized_keys_content: raise Exception("Key '{}' is not present in authorized_keys".format(key)) # don't delete the previous comment because we can't verify if it's legit # this regex approach failed for some reasons and I don't know why :( # authorized_keys_content = re.sub("{} *\n?".format(key), # "", # authorized_keys_content, # flags=re.MULTILINE) authorized_keys_content = authorized_keys_content.replace(key, "") write_to_file(authorized_keys_file, authorized_keys_content)
def test_write_to_new_file(): new_file = "%s/barfile" % TMP_TEST_DIR assert not os.path.exists(new_file) write_to_file(new_file, "yolo\nswag") assert os.path.exists(new_file) assert read_file(new_file) == "yolo\nswag"
def test_write_to_new_file_badpermissions(): switch_to_non_root_user() new_file = "%s/barfile" % TMP_TEST_DIR assert not os.path.exists(new_file) with pytest.raises(MoulinetteError): write_to_file(new_file, "yolo\nswag")
def user_ssh_add_key(username, key, comment): user = _get_user_for_ssh(username, ["homeDirectory", "uid"]) if not user: raise Exception("User with username '%s' doesn't exists" % username) authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys") if not os.path.exists(authorized_keys_file): # ensure ".ssh" exists mkdir(os.path.join(user["homeDirectory"][0], ".ssh"), force=True, parents=True, uid=user["uid"][0]) # create empty file to set good permissions write_to_file(authorized_keys_file, "") chown(authorized_keys_file, uid=user["uid"][0]) chmod(authorized_keys_file, 0o600) authorized_keys_content = read_file(authorized_keys_file) authorized_keys_content += "\n" authorized_keys_content += "\n" if comment and comment.strip(): if not comment.lstrip().startswith("#"): comment = "# " + comment authorized_keys_content += comment.replace("\n", " ").strip() authorized_keys_content += "\n" authorized_keys_content += key.strip() authorized_keys_content += "\n" write_to_file(authorized_keys_file, authorized_keys_content)
def test_write_to_new_file(tmp_path): new_file = tmp_path / "newfile.txt" write_to_file(str(new_file), "yolo\nswag") assert os.path.exists(str(new_file)) assert read_file(str(new_file)) == "yolo\nswag"
def test_write_to_file_exception(test_file, mocker): error = "foobar" mocker.patch("builtins.open", side_effect=Exception(error)) with pytest.raises(MoulinetteError) as exception: write_to_file(str(test_file), "yolo\nswag") translation = m18n.g("error_writing_file", file=str(test_file), error=error) expected_msg = translation.format(file=str(test_file), error=error) assert expected_msg in str(exception)
def get_public_ip(protocol=4): assert protocol in [4, 6], "Invalid protocol version for get_public_ip: %s, expected 4 or 6" % protocol cache_file = "/var/cache/yunohost/ipv%s" % protocol cache_duration = 120 # 2 min if os.path.exists(cache_file) and abs(os.path.getctime(cache_file) - time.time()) < cache_duration: ip = read_file(cache_file).strip() ip = ip if ip else None # Empty file (empty string) means there's no IP logger.debug("Reusing IPv%s from cache: %s" % (protocol, ip)) else: ip = get_public_ip_from_remote_server(protocol) logger.debug("IP fetched: %s" % ip) write_to_file(cache_file, ip or "") return ip
def domain_main_domain(operation_logger, new_main_domain=None): """ Check the current main domain, or change it Keyword argument: new_main_domain -- The new domain to be set as the main domain """ from yunohost.tools import _set_hostname # If no new domain specified, we return the current main domain if not new_main_domain: return {"current_main_domain": _get_maindomain()} # Check domain exists if new_main_domain not in domain_list()["domains"]: raise YunohostValidationError("domain_name_unknown", domain=new_main_domain) operation_logger.related_to.append(("domain", new_main_domain)) operation_logger.start() # Apply changes to ssl certs try: write_to_file("/etc/yunohost/current_host", new_main_domain) _set_hostname(new_main_domain) except Exception as e: logger.warning("%s" % e, exc_info=1) raise YunohostError("main_domain_change_failed") # Generate SSOwat configuration file app_ssowatconf() # Regen configurations if os.path.exists("/etc/yunohost/installed"): regen_conf() logger.success(m18n.n("main_domain_changed"))
def test_write_to_existing_file_badpermissions(): assert os.path.exists(TMP_TEST_FILE) switch_to_non_root_user() with pytest.raises(MoulinetteError): write_to_file(TMP_TEST_FILE, "yolo\nswag")
def test_write_to_existing_file(): assert os.path.exists(TMP_TEST_FILE) write_to_file(TMP_TEST_FILE, "yolo\nswag") assert read_file(TMP_TEST_FILE) == "yolo\nswag"
def test_write_json_inside_nonexistent_folder(): with pytest.raises(AssertionError): write_to_file("/toto/test.json", ["a", "b"])
def test_write_to_file_with_a_list(): assert os.path.exists(TMP_TEST_FILE) write_to_file(TMP_TEST_FILE, ["yolo", "swag"]) assert read_file(TMP_TEST_FILE) == "yolo\nswag"
def test_write_inside_nonexistent_folder(): with pytest.raises(AssertionError): write_to_file("/toto/test", "yolo\nswag")
def test_write_to_folder(): with pytest.raises(AssertionError): write_to_file(TMP_TEST_DIR, "yolo\nswag")
def _remove_lock(PID_to_remove): # FIXME ironically not concurrency safe because it's not atomic... PIDs = read_file(MOULINETTE_LOCK).split("\n") PIDs_to_keep = [PID for PID in PIDs if int(PID) != PID_to_remove] write_to_file(MOULINETTE_LOCK, "\n".join(PIDs_to_keep))
def test_write_to_file_with_a_list(test_file): write_to_file(str(test_file), ["yolo", "swag"]) assert read_file(str(test_file)) == "yolo\nswag"
def test_write_cannot_write_to_non_existant_folder(): with pytest.raises(AssertionError): write_to_file("/toto/test", "yolo\nswag")
def test_write_cannot_write_folder(tmp_path): with pytest.raises(AssertionError): write_to_file(str(tmp_path), "yolo\nswag")
def dyndns_update( operation_logger, dyn_host="dyndns.yunohost.org", domain=None, key=None, ipv4=None, ipv6=None, force=False, dry_run=False, ): """ Update IP on DynDNS platform Keyword argument: domain -- Full domain to update dyn_host -- Dynette DNS server to inform key -- Public DNS key ipv4 -- IP address to send ipv6 -- IPv6 address to send """ # Get old ipv4/v6 old_ipv4, old_ipv6 = (None, None) # (default values) # If domain is not given, try to guess it from keys available... if domain is None: (domain, key) = _guess_current_dyndns_domain(dyn_host) if domain is None: raise YunohostValidationError('dyndns_no_domain_registered') # If key is not given, pick the first file we find with the domain given else: if key is None: keys = glob.glob( "/etc/yunohost/dyndns/K{0}.+*.private".format(domain)) if not keys: raise YunohostValidationError("dyndns_key_not_found") key = keys[0] # Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me' host = domain.split(".")[1:] host = ".".join(host) logger.debug("Building zone update file ...") lines = [ "server %s" % dyn_host, "zone %s" % host, ] def resolve_domain(domain, rdtype): # FIXME make this work for IPv6-only hosts too.. ok, result = dig(dyn_host, "A") dyn_host_ip = result[0] if ok == "ok" and len(result) else None if not dyn_host_ip: raise YunohostError("Failed to resolve %s" % dyn_host) ok, result = dig(domain, rdtype, resolvers=[dyn_host_ip]) if ok == "ok": return result[0] if len(result) else None elif result[0] == "Timeout": logger.debug( "Timed-out while trying to resolve %s record for %s using %s" % (rdtype, domain, dyn_host)) else: return None logger.debug("Falling back to external resolvers") ok, result = dig(domain, rdtype, resolvers="force_external") if ok == "ok": return result[0] if len(result) else None elif result[0] == "Timeout": logger.debug( "Timed-out while trying to resolve %s record for %s using external resolvers : %s" % (rdtype, domain, result)) else: return None raise YunohostError("Failed to resolve %s for %s" % (rdtype, domain), raw_msg=True) old_ipv4 = resolve_domain(domain, "A") old_ipv6 = resolve_domain(domain, "AAAA") # Get current IPv4 and IPv6 ipv4_ = get_public_ip() ipv6_ = get_public_ip(6) if ipv4 is None: ipv4 = ipv4_ if ipv6 is None: ipv6 = ipv6_ logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6)) logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6)) # no need to update if (not force and not dry_run) and (old_ipv4 == ipv4 and old_ipv6 == ipv6): logger.info("No updated needed.") return else: operation_logger.related_to.append(("domain", domain)) operation_logger.start() logger.info("Updated needed, going on...") dns_conf = _build_dns_conf(domain) # Delete custom DNS records, we don't support them (have to explicitly # authorize them on dynette) for category in dns_conf.keys(): if category not in ["basic", "mail", "xmpp", "extra"]: del dns_conf[category] # Delete the old records for all domain/subdomains # every dns_conf.values() is a list of : # [{"name": "...", "ttl": "...", "type": "...", "value": "..."}] for records in dns_conf.values(): for record in records: action = "update delete {name}.{domain}.".format(domain=domain, **record) action = action.replace(" @.", " ") lines.append(action) # Add the new records for all domain/subdomains for records in dns_conf.values(): for record in records: # (For some reason) here we want the format with everytime the # entire, full domain shown explicitly, not just "muc" or "@", it # should be muc.the.domain.tld. or the.domain.tld if record["value"] == "@": record["value"] = domain record["value"] = record["value"].replace(";", r"\;") action = "update add {name}.{domain}. {ttl} {type} {value}".format( domain=domain, **record) action = action.replace(" @.", " ") lines.append(action) lines += ["show", "send"] # Write the actions to do to update to a file, to be able to pass it # to nsupdate as argument write_to_file(DYNDNS_ZONE, "\n".join(lines)) logger.debug("Now pushing new conf to DynDNS host...") if not dry_run: try: command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE] subprocess.check_call(command) except subprocess.CalledProcessError: raise YunohostError("dyndns_ip_update_failed") logger.success(m18n.n("dyndns_ip_updated")) else: print(read_file(DYNDNS_ZONE)) print("") print( "Warning: dry run, this is only the generated config, it won't be applied" )
def test_write_to_existing_file(test_file): write_to_file(str(test_file), "yolo\nswag") assert read_file(str(test_file)) == "yolo\nswag"