protected void Page_Load(object sender, EventArgs e) { try { if (Request.HttpMethod == "POST") { byte[] buffer = new byte[Request.InputStream.Length]; Request.InputStream.Read(buffer, 0, buffer.Length); // Notification data is supplied in Base64 format, to avoid digital signature encoding issues string data = Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.ASCII.GetString(buffer))); // Load the notification data into an XmlDocument XmlDocument notification = new XmlDocument(); notification.LoadXml(data); // Items in the notification XML are in the Sage SSO namespace, so we need to add an // XmlNamespaceManager here. See the schema documentation for Notification for more details on // the notification XML format. XmlNamespaceManager nsmgr = new XmlNamespaceManager(notification.NameTable); nsmgr.AddNamespace("sso", "http://sso.sage.com"); CheckNotificationValidSignature(notification); CheckNotificationNotExpired(notification, nsmgr); CheckNotificationNotReplayed(notification, nsmgr); // This XPath expression will only return an XmlNode if the notification type node contains "Session.Ended" and there // is a parameter with the name "SessionId". XmlNode sessionIdNode = notification.SelectSingleNode("/sso:Notification[sso:Type=\"Session.Ended\"]/sso:Parameters/sso:Parameter[sso:Name=\"SessionId\"]/sso:Value", nsmgr); if (sessionIdNode != null) { // Remove the SSO session from the session map. The next time a page is loaded for that SSO session ID, // the map is checked and, if the session is not found the application session is ended. SSOSessionMap.EndSSOSession(new Guid(sessionIdNode.InnerText)); } // This XPath expression will only return an XmlNode if the notification type node contains "Session.ExpiryDue" and there // is a parameter with the name "SessionId". sessionIdNode = notification.SelectSingleNode("/sso:Notification[sso:Type=\"Session.ExpiryDue\"]/sso:Parameters/sso:Parameter[sso:Name=\"SessionId\"]/sso:Value", nsmgr); if (sessionIdNode != null) { // The session will expire shortly. We need to determine whether we need to // extend the session in Sage SSO and act accordingly. Guid ssoSessionId = new Guid(sessionIdNode.InnerText); if (SSOSessionMap.HasSSOSession(ssoSessionId)) { // This XPath expression returns the Timestamp parameter value in the notification. XmlNode ssoSessionExpiryDueNode = notification.SelectSingleNode("/sso:Notification/sso:Parameters/sso:Parameter[sso:Name=\"Timestamp\"]/sso:Value", nsmgr); // The timestamp, like all Sage SSO timestamps, is in XSD schema format and is UTC. DateTime ssoSessionExpiryDue = XmlConvert.ToDateTime(ssoSessionExpiryDueNode.InnerText, XmlDateTimeSerializationMode.RoundtripKind); DateTime newSSOSessionExpiry = DateTime.MinValue; // If there has been some user activity on this session we'll call Sage SSO to extend the // SSO session to match the application session expiry time based on the last activity // and the application session timeout setting. if (SSOSessionMap.ShouldExtendSSOSession(ssoSessionId, ssoSessionExpiryDue, out newSSOSessionExpiry)) { using (WebSSOServiceSoapClient client = new WebSSOServiceSoapClient("WebSSOServiceSoapClient")) { client.Open(); SessionExtendRequest request = new SessionExtendRequest() { SessionId = ssoSessionId, SessionExpiry = newSSOSessionExpiry, SessionExpirySpecified = true }; try { // Call Sage SSO to extend the SSO session to match our application session SessionExtendResponse response = client.SessionExtend(request); // Extend the SSO session in the session map to synchronise with what Sage SSO // tells us. It's important to observe the session expiry that's returned // because the Sage SSO session may be nearing its hard timeout and we // don't want to extend the application session beyond that. SSOSessionMap.ExtendSSOSession(ssoSessionId, response.SessionExpiry); // The ExpiryDue flag tells us whether this session is in its expiry period. If // ExpiryDue returns true at this point either we haven't extended the session far enough // past the expiry due threshold to cause Sage SSO to unmark the session (perhaps // because there was some activity right at the start of the application session but // nothing since) or the SSO session is approaching its hard timeout. // // If the flag is false then we'll receive another notification before the session expires, // if it's true then we WON'T receive another notification before the session expires and // we must call Sage SSO to extend the session if there's any activity. SSOSessionMap.SetShouldExtendSSOSessionOnUserActivity(ssoSessionId, response.ExpiryDue); } catch (Exception) { // There was a problem extending the session on Sage SSO, or some other local problem // with the SSO session map or application session state. The safest thing to do // at this point is to end the application session. SSOSessionMap.EndSSOSession(ssoSessionId); } } } else { // We're not extending the SSO session right now but we mark the mapped SSO session // as requiring extension on user activity. If the user makes a call into the // application while the session is marked, we call Sage SSO to extend the session and un-mark // the application session. You can see this code in PageBase.cs in the OnLoad() method. // The application will receive another notification when the Sage SSO session is next due to expire. SSOSessionMap.SetShouldExtendSSOSessionOnUserActivity(ssoSessionId, true); } } } } } catch (Exception) { // We catch all exceptions here so we don't return a 500 Internal Server Error to Sage SSO. // In a production system, it would be a good idea to log these. } }
// Check for the existence of an SSO session, record the activity and extend it by calling SSO, if needed. Refresh // should be called each time a page is loaded (or if any Ajax service is called). Returns true if the SSO // session was successfully refreshed. public static bool Refresh() { bool refreshed = false; if (HasSession) { Guid ssoSessionId = Current.SSOSessionId; if (SSOSessionMap.ShouldExtendSSOSessionOnUserActivity(ssoSessionId)) { // The Sage SSO session associated with this application session has been marked expiry due since // the last time there was activity. Call Sage SSO to extend the session and then clear the // mark so that we don't call SessionExtend again until we receive another Session.ExpiryDue // notification. using (WebSSOServiceSoapClient client = new WebSSOServiceSoapClient("WebSSOServiceSoapClient")) { client.Open(); SessionExtendRequest request = new SessionExtendRequest() { SessionId = ssoSessionId, SessionExpiry = DateTime.UtcNow + TimeSpan.FromMinutes(HttpContext.Current.Session.Timeout), SessionExpirySpecified = true }; try { // Call Sage SSO to extend the session to match our application session. SessionExtendResponse response = client.SessionExtend(request); Current.AuthenticationToken = response.UserAuthenticationToken; // Extend the SSO session in the session map to synchronise with what Sage SSO // tells us. It's important to observe the session expiry that's returned // because the Sage SSO session may be nearing its hard timeout and we // don't want to extend the application session beyond that. SSOSessionMap.ExtendSSOSession(ssoSessionId, response.SessionExpiry); // Unlike when we handle the Session.ExpiryDue notification, we always clear // the expiry due flag here to avoid making continual calls to Sage SSO // if the SSO session is nearing the hard timeout. SSOSessionMap.SetShouldExtendSSOSessionOnUserActivity(ssoSessionId, false); refreshed = true; } catch (Exception) { // There was a problem extending the session on Sage SSO, or some other local problem // with the SSO session map or application session state. The safest thing to do // at this point is to end the application session. Current.End(); } } } else { try { // Record the activity in the SSO session map. SSOSessionMap.RefreshSSOSession(ssoSessionId); refreshed = true; } catch (Exception) { // There was a problem refreshing the SSO session in the SSO session map. The safest thing // to do at this point is to end the application session. Current.End(); } } } return refreshed; }