finish06 / pyunifi

https://unifi-sdn.ubnt.com/
MIT License
223 stars 99 forks source link

2FA Support #85

Open caboose014 opened 3 months ago

caboose014 commented 3 months ago

I have figured out what needs to be done for the 2FA changes that are coming into effect in July 22nd '24.

Here is the section of code I modified for the login to allow the passing of the 2FA token:

def _login(self):
        self.log.debug("login() as %s", self.username)
        self.session = requests.Session()
        self.session.verify = self.ssl_verify

        response = self.session.post(
            self.auth_url,
            data=json.dumps({"username": self.username, "password": self.password, "ubic_2fa_token": self.token}),
            headers=self.headers,
        )

        print(response.content)

        if response.headers.get("X-CSRF-Token"):
            self.headers = {"X-CSRF-Token": response.headers["X-CSRF-Token"]}
        if response.status_code == 400:
            raise APIError(
                "Login failed - status code: %i, msg: %s" % (response.status_code, response.json()["meta"]["msg"])
                )

        if response.status_code != 200:
            raise APIError(
                "Login failed - status code: %i, msg: %s" % (response.status_code, response.text)
                )

I pass this to the controller using this:

mfa_token = input("Enter your 2FA Token: ")

c = Controller('-Unifi Controller-', '-username-', '-password-', mfa_token)

You also need to add token=0 to the start of the init definition, I put this after the password entry, then you'll need to define the self variable down further using self.token = token

Passing json in the session.post command was not sending in the correct format for this. it needs to be sent as data, and decoded from JSON. I have checked that this still does work with accounts without the 2FA enabled on it, you can either just not pass the 2FA argument, or send any value you like - it will be ignored.

Hope this helps those looking for an answer for this!

jhavens12 commented 2 months ago

Hi thanks for this - one of my controllers (UDM SE) does not ask for a MFA code each time I run the unmodified script, but my other controller (Unifi Express) fails to log in and I get the pop up on my unifi MFA app asking to approve login - which I do, but it obviously doesn't work.

I updated the file as you have above and installed it, but do not understand how to not put in the MFA code each time the script is run. Can you post an example? I'm just trying to run something basic like printing the connected client's IPs.

Thanks

caboose014 commented 1 month ago

Hi jhavens12,

The following bit of code prompts you for your 6 digit code that gets generated by your authentication app:

mfa_token = input("Enter your 2FA Token: ")

Once you type in your code, the script will continue. This is how its setup from what I have written, as I do not have my accounts prompting for authentication prompts via the app - only though an authenticator app.