55# Integrate Airflow with LDAP or OAUTH
66
77> 🟥 __ Warning__ 🟥
8- >
8+ >
99> The ` AUTH_ROLES_MAPPING ` feature requires ` Flask-Appbuilder>=3.2.0 ` .
1010> Starting from Airflow 2.0.2, ` Flask-Appbuilder>=3.2.0 ` is included by default,
1111> older versions of airflow will require you to [ manually install] ( ../configuration/extra-python-packages.md ) ` Flask-AppBuilder>=3.2.0 ` .
1212
1313> 🟦 __ Tip__ 🟦
1414>
1515> After integrating with LDAP or OAUTH, you should:
16- >
16+ >
1717> 1 . Set the ` airflow.users ` value to ` [] `
1818> 2 . Manually delete any previously created users (with the airflow WebUI)
1919
5656 # only needed for airflow 1.10
5757 #from airflow import configuration as conf
5858 #SQLALCHEMY_DATABASE_URI = conf.get("core", "SQL_ALCHEMY_CONN")
59-
59+
6060 AUTH_TYPE = AUTH_LDAP
6161 AUTH_LDAP_SERVER = "ldap://ldap.example.com"
6262 AUTH_LDAP_USE_TLS = False
63-
63+
6464 # registration configs
6565 AUTH_USER_REGISTRATION = True # allow users who are not already in the FAB DB
6666 AUTH_USER_REGISTRATION_ROLE = "Public" # this role will be given in addition to any AUTH_ROLES_MAPPING
6767 AUTH_LDAP_FIRSTNAME_FIELD = "givenName"
6868 AUTH_LDAP_LASTNAME_FIELD = "sn"
6969 AUTH_LDAP_EMAIL_FIELD = "mail" # if null in LDAP, email is set to: "{username}@email.notfound"
70-
70+
7171 # bind username (for password validation)
7272 AUTH_LDAP_USERNAME_FORMAT = "uid=%s,ou=users,dc=example,dc=com" # %s is replaced with the provided username
7373 # AUTH_LDAP_APPEND_DOMAIN = "example.com" # bind usernames will look like: {USERNAME}@example.com
74-
74+
7575 # search configs
7676 AUTH_LDAP_SEARCH = "ou=users,dc=example,dc=com" # the LDAP search base (if non-empty, a search will ALWAYS happen)
7777 AUTH_LDAP_UID_FIELD = "uid" # the username field
8181 "cn=airflow_users,ou=groups,dc=example,dc=com": ["User"],
8282 "cn=airflow_admins,ou=groups,dc=example,dc=com": ["Admin"],
8383 }
84-
84+
8585 # the LDAP user attribute which has their role DNs
8686 AUTH_LDAP_GROUP_FIELD = "memberOf"
87-
87+
8888 # if we should replace ALL the user's roles each login, or only on registration
8989 AUTH_ROLES_SYNC_AT_LOGIN = True
90-
90+
9191 # force users to re-auth after 30min of inactivity (to keep roles in sync)
9292 PERMANENT_SESSION_LIFETIME = 1800
9393` ` `
@@ -115,18 +115,18 @@ web:
115115 # only needed for airflow 1.10
116116 #from airflow import configuration as conf
117117 #SQLALCHEMY_DATABASE_URI = conf.get("core", "SQL_ALCHEMY_CONN")
118-
118+
119119 AUTH_TYPE = AUTH_LDAP
120120 AUTH_LDAP_SERVER = "ldap://ldap.example.com"
121121 AUTH_LDAP_USE_TLS = False
122-
122+
123123 # registration configs
124124 AUTH_USER_REGISTRATION = True # allow users who are not already in the FAB DB
125125 AUTH_USER_REGISTRATION_ROLE = "Public" # this role will be given in addition to any AUTH_ROLES_MAPPING
126126 AUTH_LDAP_FIRSTNAME_FIELD = "givenName"
127127 AUTH_LDAP_LASTNAME_FIELD = "sn"
128128 AUTH_LDAP_EMAIL_FIELD = "mail" # if null in LDAP, email is set to: "{username}@email.notfound"
129-
129+
130130 # search configs
131131 AUTH_LDAP_SEARCH = "ou=users,dc=example,dc=com" # the LDAP search base
132132 AUTH_LDAP_UID_FIELD = "uid" # the username field
@@ -138,13 +138,13 @@ web:
138138 "cn=airflow_users,ou=groups,dc=example,dc=com": ["User"],
139139 "cn=airflow_admins,ou=groups,dc=example,dc=com": ["Admin"],
140140 }
141-
141+
142142 # the LDAP user attribute which has their role DNs
143143 AUTH_LDAP_GROUP_FIELD = "memberOf"
144-
144+
145145 # if we should replace ALL the user's roles each login, or only on registration
146146 AUTH_ROLES_SYNC_AT_LOGIN = True
147-
147+
148148 # force users to re-auth after 30min of inactivity (to keep roles in sync)
149149 PERMANENT_SESSION_LIFETIME = 1800
150150` ` `
@@ -185,31 +185,35 @@ web:
185185 # Custom AirflowSecurityManager
186186 #######################################
187187 from airflow.www.security import AirflowSecurityManager
188-
188+
189189 class CustomSecurityManager(AirflowSecurityManager):
190190 def get_oauth_user_info(self, provider, resp):
191191 if provider == "github":
192192 user_data = self.appbuilder.sm.oauth_remotes[provider].get("user").json()
193193 emails_data = self.appbuilder.sm.oauth_remotes[provider].get("user/emails").json()
194194 teams_data = self.appbuilder.sm.oauth_remotes[provider].get("user/teams").json()
195-
196- # unpack the user's name
197- first_name = ""
198- last_name = ""
199- name = user_data.get("name", "").split(maxsplit=1)
200- if len(name) == 1:
201- first_name = name[0]
202- elif len(name) == 2:
203- first_name = name[0]
204- last_name = name[1]
205-
195+
206196 # unpack the user's email
207197 email = ""
208198 for email_data in emails_data:
209199 if email_data["primary"]:
210200 email = email_data["email"]
211201 break
212-
202+
203+ # unpack the user's name
204+ first_name = ""
205+ last_name = ""
206+ raw_name = user_data.get("name")
207+ if raw_name:
208+ name_parts = raw_name.split(maxsplit=1)
209+ if len(name_parts) == 1:
210+ first_name = name_parts[0]
211+ elif len(name_parts) == 2:
212+ first_name, last_name = name_parts
213+ elif email:
214+ # set user name from email address
215+ first_name = email.split("@")[0]
216+
213217 # unpack the user's teams as role_keys
214218 # NOTE: each role key will be "my-github-org/my-team-name"
215219 role_keys = []
218222 team_slug = team_data["slug"]
219223 team_ref = team_org + "/" + team_slug
220224 role_keys.append(team_ref)
221-
225+
222226 return {
223227 "username": "github_" + user_data.get("login", ""),
224228 "first_name": first_name,
@@ -228,23 +232,23 @@ web:
228232 }
229233 else:
230234 return {}
231-
235+
232236 #######################################
233237 # Actual `webserver_config.py`
234238 #######################################
235239 from flask_appbuilder.security.manager import AUTH_OAUTH
236-
240+
237241 # only needed for airflow 1.10
238242 #from airflow import configuration as conf
239243 #SQLALCHEMY_DATABASE_URI = conf.get("core", "SQL_ALCHEMY_CONN")
240-
244+
241245 AUTH_TYPE = AUTH_OAUTH
242246 SECURITY_MANAGER_CLASS = CustomSecurityManager
243-
247+
244248 # registration configs
245249 AUTH_USER_REGISTRATION = True # allow users who are not already in the FAB DB
246250 AUTH_USER_REGISTRATION_ROLE = "Public" # this role will be given in addition to any AUTH_ROLES_MAPPING
247-
251+
248252 # the list of providers which the user can choose from
249253 OAUTH_PROVIDERS = [
250254 {
@@ -261,16 +265,16 @@ web:
261265 },
262266 },
263267 ]
264-
268+
265269 # a mapping from the values of `userinfo["role_keys"]` to a list of FAB roles
266270 AUTH_ROLES_MAPPING = {
267271 "my-github-org/airflow-users-team": ["User"],
268272 "my-github-org/airflow-admin-team": ["Admin"],
269273 }
270-
274+
271275 # if we should replace ALL the user's roles each login, or only on registration
272276 AUTH_ROLES_SYNC_AT_LOGIN = True
273-
277+
274278 # force users to re-auth after 30min of inactivity (to keep roles in sync)
275279 PERMANENT_SESSION_LIFETIME = 1800
276280` ` `
337341 PERMANENT_SESSION_LIFETIME = 1800
338342` ` `
339343
340- </details>
344+ </details>
0 commit comments