From f1a01aff6ca8136e4e38a1ec4fbc3f0f9fb9965f Mon Sep 17 00:00:00 2001 From: 32-Levels Date: Tue, 14 Apr 2020 19:44:29 -0400 Subject: [PATCH 1/6] August-W: added setters in sp.py for logout_return_endpoint, default_login_return_endpoint, and acs_redirect_endpoint, fixed issue with scheme in views to allow for https, and updated the example sp.py accordingly --- examples/sp.py | 13 +++-------- flask_saml2/sp/sp.py | 52 ++++++++++++++++++++++++++++++++++------- flask_saml2/sp/views.py | 8 +++++-- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/examples/sp.py b/examples/sp.py index 6409321..018acb1 100755 --- a/examples/sp.py +++ b/examples/sp.py @@ -5,16 +5,9 @@ from tests.idp.base import CERTIFICATE as IDP_CERTIFICATE from tests.sp.base import CERTIFICATE, PRIVATE_KEY - -class ExampleServiceProvider(ServiceProvider): - def get_logout_return_url(self): - return url_for('index', _external=True) - - def get_default_login_return_url(self): - return url_for('index', _external=True) - - -sp = ExampleServiceProvider() +sp = ServiceProvider() +sp.set_default_login_return_endpoint('index') +sp.set_logout_return_endpoint('index') app = Flask(__name__) app.debug = True diff --git a/flask_saml2/sp/sp.py b/flask_saml2/sp/sp.py index 7535958..cd53d0b 100644 --- a/flask_saml2/sp/sp.py +++ b/flask_saml2/sp/sp.py @@ -34,6 +34,11 @@ class ServiceProvider: #: The name of the blueprint to generate. blueprint_name = 'flask_saml2_sp' + scheme = 'http' + logout_return_endpoint = None + default_login_return_endpoint = None + acs_redirect_endpoint = None + def login_successful( self, auth_data: AuthData, @@ -153,10 +158,15 @@ def get_metadata_url(self) -> str: """ return url_for(self.blueprint_name + '.metadata', _external=True) + def set_default_login_return_endpoint(self, endpoint) -> Optional[str]: + """Set the default URL to redirect users to once the have logged in. + """ + self.default_login_return_endpoint = endpoint + def get_default_login_return_url(self) -> Optional[str]: """The default URL to redirect users to once the have logged in. """ - return None + return url_for(self.default_login_return_endpoint, _external=True) def get_login_return_url(self) -> Optional[str]: """Get the URL to redirect the user to now that they have logged in. @@ -174,10 +184,15 @@ def get_login_return_url(self) -> Optional[str]: return None + def set_logout_return_endpoint(self, endpoint): + """Set the URL to redirect the user to now that they have logged out. + """ + self.logout_return_endpoint = endpoint + def get_logout_return_url(self) -> Optional[str]: """The URL to redirect users to once they have logged out. """ - return None + return url_for(self.logout_return_endpoint, _external=True) def is_valid_redirect_url(self, url: str) -> str: """Is this URL valid and safe to redirect to? @@ -306,22 +321,41 @@ def get_metadata_context(self) -> dict: 'contacts': [], } - def create_blueprint(self) -> Blueprint: + def set_scheme(self, scheme): + self.scheme = scheme + + def get_scheme(self) -> str: + return self.scheme + + def set_acs_redirect_endpoint(self, acs_redirect_endpoint): + self.acs_redirect_endpoint = acs_redirect_endpoint + + def get_acs_redirect_endpoint(self) -> str: + return self.acs_redirect_endpoint + + #With acs_redirect_url, you can set the url that the Access Consumer Service redirects to upon successful login + #This is unnecessary if you expect a "relay_state" parameter in the SAML request to the ACS + def create_blueprint(self, login_endpoint='/login/', login_idp_endpoint='/login/idp/', \ + logout_endpoint='/logout/', acs_endpoint='/acs/', sls_endpoint='/sls/', \ + metadata_endpoint='/metadata.xml', scheme='http') -> Blueprint: + """Create a Flask :class:`flask.Blueprint` for this Service Provider. """ + self.set_scheme(scheme) + idp_bp = Blueprint(self.blueprint_name, 'flask_saml2.sp', template_folder='templates') - idp_bp.add_url_rule('/login/', view_func=Login.as_view( + idp_bp.add_url_rule(login_endpoint, view_func=Login.as_view( 'login', sp=self)) - idp_bp.add_url_rule('/login/idp/', view_func=LoginIdP.as_view( + idp_bp.add_url_rule(login_idp_endpoint, view_func=LoginIdP.as_view( 'login_idp', sp=self)) - idp_bp.add_url_rule('/logout/', view_func=Logout.as_view( + idp_bp.add_url_rule(logout_endpoint, view_func=Logout.as_view( 'logout', sp=self)) - idp_bp.add_url_rule('/acs/', view_func=AssertionConsumer.as_view( + idp_bp.add_url_rule(acs_endpoint, view_func=AssertionConsumer.as_view( 'acs', sp=self)) - idp_bp.add_url_rule('/sls/', view_func=SingleLogout.as_view( + idp_bp.add_url_rule(sls_endpoint, view_func=SingleLogout.as_view( 'sls', sp=self)) - idp_bp.add_url_rule('/metadata.xml', view_func=Metadata.as_view( + idp_bp.add_url_rule(metadata_endpoint, view_func=Metadata.as_view( 'metadata', sp=self)) idp_bp.register_error_handler(CannotHandleAssertion, CannotHandleAssertionView.as_view( diff --git a/flask_saml2/sp/views.py b/flask_saml2/sp/views.py index 1c116b6..8263603 100644 --- a/flask_saml2/sp/views.py +++ b/flask_saml2/sp/views.py @@ -28,7 +28,8 @@ def get(self): handler = self.sp.get_default_idp_handler() login_next = self.sp.get_login_return_url() if handler: - return redirect(url_for('.login_idp', entity_id=handler.entity_id, next=login_next)) + return redirect(url_for('.login_idp', entity_id=handler.entity_id, next=login_next, + _scheme=self.sp.get_scheme(), _external=True)) return self.sp.render_template( 'flask_saml2_sp/choose_idp.html', login_next=login_next, @@ -79,7 +80,10 @@ def do_logout(self, handler): class AssertionConsumer(SAML2View): def post(self): saml_request = request.form['SAMLResponse'] - relay_state = request.form['RelayState'] + if self.sp.get_acs_redirect_endpoint() == None: + relay_state = request.form['RelayState'] + else: + relay_state = self.sp.make_absolute_url(self.sp.get_acs_redirect_endpoint()) for handler in self.sp.get_idp_handlers(): try: From c29c2999cf924b7512628bcee61b94424f79f381 Mon Sep 17 00:00:00 2001 From: 32-Levels Date: Tue, 14 Apr 2020 20:25:38 -0400 Subject: [PATCH 2/6] August-W: Added checks for None-type endpoints in get_default_login_return_url and get_logout_return_url in sp.py --- flask_saml2/sp/sp.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flask_saml2/sp/sp.py b/flask_saml2/sp/sp.py index cd53d0b..5f26b18 100644 --- a/flask_saml2/sp/sp.py +++ b/flask_saml2/sp/sp.py @@ -166,7 +166,9 @@ def set_default_login_return_endpoint(self, endpoint) -> Optional[str]: def get_default_login_return_url(self) -> Optional[str]: """The default URL to redirect users to once the have logged in. """ - return url_for(self.default_login_return_endpoint, _external=True) + if self.default_login_return_endpoint!=None: + return url_for(self.default_login_return_endpoint, _external=True) + return None def get_login_return_url(self) -> Optional[str]: """Get the URL to redirect the user to now that they have logged in. @@ -192,7 +194,9 @@ def set_logout_return_endpoint(self, endpoint): def get_logout_return_url(self) -> Optional[str]: """The URL to redirect users to once they have logged out. """ - return url_for(self.logout_return_endpoint, _external=True) + if self.logout_return_endpoint!=None: + return url_for(self.logout_return_endpoint, _external=True) + return None def is_valid_redirect_url(self, url: str) -> str: """Is this URL valid and safe to redirect to? From a419ab6faf6beca99e7939fef6ef56617fb15832 Mon Sep 17 00:00:00 2001 From: 32-Levels Date: Tue, 14 Apr 2020 20:50:09 -0400 Subject: [PATCH 3/6] August-W: cleaned code as per flake8 recommendations --- flask_saml2/sp/sp.py | 18 +++++++++--------- flask_saml2/sp/views.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/flask_saml2/sp/sp.py b/flask_saml2/sp/sp.py index 5f26b18..7c4e581 100644 --- a/flask_saml2/sp/sp.py +++ b/flask_saml2/sp/sp.py @@ -166,7 +166,7 @@ def set_default_login_return_endpoint(self, endpoint) -> Optional[str]: def get_default_login_return_url(self) -> Optional[str]: """The default URL to redirect users to once the have logged in. """ - if self.default_login_return_endpoint!=None: + if self.default_login_return_endpoint is not None: return url_for(self.default_login_return_endpoint, _external=True) return None @@ -194,7 +194,7 @@ def set_logout_return_endpoint(self, endpoint): def get_logout_return_url(self) -> Optional[str]: """The URL to redirect users to once they have logged out. """ - if self.logout_return_endpoint!=None: + if self.logout_return_endpoint is not None: return url_for(self.logout_return_endpoint, _external=True) return None @@ -327,21 +327,21 @@ def get_metadata_context(self) -> dict: def set_scheme(self, scheme): self.scheme = scheme - + def get_scheme(self) -> str: return self.scheme def set_acs_redirect_endpoint(self, acs_redirect_endpoint): self.acs_redirect_endpoint = acs_redirect_endpoint - + def get_acs_redirect_endpoint(self) -> str: return self.acs_redirect_endpoint - #With acs_redirect_url, you can set the url that the Access Consumer Service redirects to upon successful login - #This is unnecessary if you expect a "relay_state" parameter in the SAML request to the ACS - def create_blueprint(self, login_endpoint='/login/', login_idp_endpoint='/login/idp/', \ - logout_endpoint='/logout/', acs_endpoint='/acs/', sls_endpoint='/sls/', \ - metadata_endpoint='/metadata.xml', scheme='http') -> Blueprint: + # With acs_redirect_url, you can set the url that the Access Consumer Service redirects to upon successful login + # This is unnecessary if you expect a "relay_state" parameter in the SAML request to the ACS + def create_blueprint(self, login_endpoint='/login/', login_idp_endpoint='/login/idp/', + logout_endpoint='/logout/', acs_endpoint='/acs/', sls_endpoint='/sls/', + metadata_endpoint='/metadata.xml', scheme='http') -> Blueprint: """Create a Flask :class:`flask.Blueprint` for this Service Provider. """ diff --git a/flask_saml2/sp/views.py b/flask_saml2/sp/views.py index 8263603..622a310 100644 --- a/flask_saml2/sp/views.py +++ b/flask_saml2/sp/views.py @@ -80,7 +80,7 @@ def do_logout(self, handler): class AssertionConsumer(SAML2View): def post(self): saml_request = request.form['SAMLResponse'] - if self.sp.get_acs_redirect_endpoint() == None: + if self.sp.get_acs_redirect_endpoint() is None: relay_state = request.form['RelayState'] else: relay_state = self.sp.make_absolute_url(self.sp.get_acs_redirect_endpoint()) From 0a112a7e17bf898ca0e7df75a2650b056accae7c Mon Sep 17 00:00:00 2001 From: 32-Levels Date: Tue, 14 Apr 2020 20:55:42 -0400 Subject: [PATCH 4/6] August-W: cleaned code as per flake8 recommendations again --- flask_saml2/sp/sp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flask_saml2/sp/sp.py b/flask_saml2/sp/sp.py index 7c4e581..1d29935 100644 --- a/flask_saml2/sp/sp.py +++ b/flask_saml2/sp/sp.py @@ -339,9 +339,9 @@ def get_acs_redirect_endpoint(self) -> str: # With acs_redirect_url, you can set the url that the Access Consumer Service redirects to upon successful login # This is unnecessary if you expect a "relay_state" parameter in the SAML request to the ACS - def create_blueprint(self, login_endpoint='/login/', login_idp_endpoint='/login/idp/', - logout_endpoint='/logout/', acs_endpoint='/acs/', sls_endpoint='/sls/', - metadata_endpoint='/metadata.xml', scheme='http') -> Blueprint: + def create_blueprint(self, login_endpoint='/login/', login_idp_endpoint='/login/idp/', + logout_endpoint='/logout/', acs_endpoint='/acs/', sls_endpoint='/sls/', + metadata_endpoint='/metadata.xml', scheme='http') -> Blueprint: """Create a Flask :class:`flask.Blueprint` for this Service Provider. """ From 57b9eb69ad1eb53c115d9d8aa158ace76c0be891 Mon Sep 17 00:00:00 2001 From: 32-Levels Date: Wed, 15 Apr 2020 15:37:46 -0400 Subject: [PATCH 5/6] August-W: added setter for entity_id in ServiceProvider so people can use their chosen value as entity_id rather than the default url to an xml file --- flask_saml2/sp/sp.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/flask_saml2/sp/sp.py b/flask_saml2/sp/sp.py index 1d29935..bb07136 100644 --- a/flask_saml2/sp/sp.py +++ b/flask_saml2/sp/sp.py @@ -38,6 +38,7 @@ class ServiceProvider: logout_return_endpoint = None default_login_return_endpoint = None acs_redirect_endpoint = None + entity_id = None def login_successful( self, @@ -82,13 +83,19 @@ def get_sp_config(self) -> dict: """ return current_app.config['SAML2_SP'] + def set_sp_entity_id(self, entity_id: str): + self.entity_id = entity_id + def get_sp_entity_id(self) -> str: """The unique identifier for this Service Provider. By default, this uses the metadata URL for this SP. See :func:`get_metadata_url`. """ - return self.get_metadata_url() + if self.entity_id is None: + return self.get_metadata_url() + else: + return self.entity_id def get_sp_certificate(self) -> Optional[X509]: """Get the public certificate for this SP.""" @@ -158,7 +165,7 @@ def get_metadata_url(self) -> str: """ return url_for(self.blueprint_name + '.metadata', _external=True) - def set_default_login_return_endpoint(self, endpoint) -> Optional[str]: + def set_default_login_return_endpoint(self, endpoint: str): """Set the default URL to redirect users to once the have logged in. """ self.default_login_return_endpoint = endpoint @@ -186,7 +193,7 @@ def get_login_return_url(self) -> Optional[str]: return None - def set_logout_return_endpoint(self, endpoint): + def set_logout_return_endpoint(self, endpoint: str): """Set the URL to redirect the user to now that they have logged out. """ self.logout_return_endpoint = endpoint From 6a76c8c41bff7d1123fed6a894120e0eb2186a92 Mon Sep 17 00:00:00 2001 From: 32-Levels Date: Wed, 29 Apr 2020 17:30:47 -0400 Subject: [PATCH 6/6] August-W: removed my un-pythonic setters and added some comments --- examples/sp.py | 4 ++-- flask_saml2/sp/sp.py | 30 ++++++++++-------------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/examples/sp.py b/examples/sp.py index 018acb1..d344a06 100755 --- a/examples/sp.py +++ b/examples/sp.py @@ -6,8 +6,8 @@ from tests.sp.base import CERTIFICATE, PRIVATE_KEY sp = ServiceProvider() -sp.set_default_login_return_endpoint('index') -sp.set_logout_return_endpoint('index') +sp.default_login_return_endpoint = 'index' +sp.logout_return_endpoint = 'index' app = Flask(__name__) app.debug = True diff --git a/flask_saml2/sp/sp.py b/flask_saml2/sp/sp.py index bb07136..f57b545 100644 --- a/flask_saml2/sp/sp.py +++ b/flask_saml2/sp/sp.py @@ -34,10 +34,19 @@ class ServiceProvider: #: The name of the blueprint to generate. blueprint_name = 'flask_saml2_sp' + #: Set this to http or https scheme = 'http' + + #: Set these to your desired endpoints logout_return_endpoint = None default_login_return_endpoint = None acs_redirect_endpoint = None + + """ + Set this value to override the default metadata return value + of :meth: `get_sp_entity_id`. By setting this, you can return + only the entity_id value, rather than the url to the full metadata xml. + """ entity_id = None def login_successful( @@ -83,9 +92,6 @@ def get_sp_config(self) -> dict: """ return current_app.config['SAML2_SP'] - def set_sp_entity_id(self, entity_id: str): - self.entity_id = entity_id - def get_sp_entity_id(self) -> str: """The unique identifier for this Service Provider. By default, this uses the metadata URL for this SP. @@ -165,11 +171,6 @@ def get_metadata_url(self) -> str: """ return url_for(self.blueprint_name + '.metadata', _external=True) - def set_default_login_return_endpoint(self, endpoint: str): - """Set the default URL to redirect users to once the have logged in. - """ - self.default_login_return_endpoint = endpoint - def get_default_login_return_url(self) -> Optional[str]: """The default URL to redirect users to once the have logged in. """ @@ -193,11 +194,6 @@ def get_login_return_url(self) -> Optional[str]: return None - def set_logout_return_endpoint(self, endpoint: str): - """Set the URL to redirect the user to now that they have logged out. - """ - self.logout_return_endpoint = endpoint - def get_logout_return_url(self) -> Optional[str]: """The URL to redirect users to once they have logged out. """ @@ -332,15 +328,9 @@ def get_metadata_context(self) -> dict: 'contacts': [], } - def set_scheme(self, scheme): - self.scheme = scheme - def get_scheme(self) -> str: return self.scheme - def set_acs_redirect_endpoint(self, acs_redirect_endpoint): - self.acs_redirect_endpoint = acs_redirect_endpoint - def get_acs_redirect_endpoint(self) -> str: return self.acs_redirect_endpoint @@ -352,7 +342,7 @@ def create_blueprint(self, login_endpoint='/login/', login_idp_endpoint='/login/ """Create a Flask :class:`flask.Blueprint` for this Service Provider. """ - self.set_scheme(scheme) + self.scheme = scheme idp_bp = Blueprint(self.blueprint_name, 'flask_saml2.sp', template_folder='templates')