edu.washington.iam.registry.ws.RelyingPartyController.java Source code

Java tutorial

Introduction

Here is the source code for edu.washington.iam.registry.ws.RelyingPartyController.java

Source

/* ========================================================================
 * Copyright (c) 2009-2011 The University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================================
 */

package edu.washington.iam.registry.ws;

import java.lang.Exception;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.BufferedWriter;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.Date;
import java.util.Vector;
import java.util.Collection;
import java.util.Iterator;
import java.text.SimpleDateFormat;

import edu.washington.iam.registry.rp.RelyingPartyEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Cookie;

import edu.washington.iam.registry.rp.RelyingParty;
import edu.washington.iam.registry.rp.RelyingPartyManager;
import edu.washington.iam.tools.XMLHelper;
import edu.washington.iam.tools.DNSVerifier;
import edu.washington.iam.tools.DNSVerifyException;
import edu.washington.iam.tools.Group;
import edu.washington.iam.tools.GroupManager;

import edu.washington.iam.registry.filter.FilterPolicyManager;
import edu.washington.iam.registry.filter.AttributeFilterPolicy;
import edu.washington.iam.registry.filter.FilterPolicyGroup;
import edu.washington.iam.registry.filter.Attribute;

import edu.washington.iam.registry.proxy.Proxy;
import edu.washington.iam.registry.proxy.ProxyIdp;
import edu.washington.iam.registry.proxy.ProxyManager;

import edu.washington.iam.registry.exception.RelyingPartyException;
import edu.washington.iam.registry.exception.FilterPolicyException;
import edu.washington.iam.registry.exception.AttributeNotFoundException;
import edu.washington.iam.registry.exception.NoPermissionException;
import edu.washington.iam.registry.exception.ProxyException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;

import java.security.cert.X509Certificate;
import java.security.cert.CertificateParsingException;

import org.springframework.mobile.device.Device;
import org.springframework.mobile.device.DeviceUtils;

@Controller
public class RelyingPartyController {

    private final Logger log = LoggerFactory.getLogger(getClass());

    public static String SECURE_LOGIN_CLASS = "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken";

    private static FilterPolicyManager filterPolicyManager;
    private static RelyingPartyManager rpManager;
    private static ProxyManager proxyManager;

    private static DNSVerifier dnsVerifier;
    private static GroupManager groupManager;
    private String adminGroupName = null;
    private Group adminGroup = null;

    public DNSVerifier getDnsVerifier() {
        return dnsVerifier;
    }

    public void setDnsVerifier(DNSVerifier v) {
        dnsVerifier = v;
    }

    public void setGroupManager(GroupManager v) {
        groupManager = v;
    }

    private MailSender mailSender;
    private SimpleMailMessage templateMessage;

    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void setTemplateMessage(SimpleMailMessage templateMessage) {
        this.templateMessage = templateMessage;
    }

    private static String browserRootPath;
    private static String certRootPath;
    private static String loginCookie;
    private static String roleCookie = "spck2";
    private static String logoutUrl;

    private static String mailTo = "pubcookie@u.washington.edu";
    private static String requestMailTo = "iam-support@u.washington.edu";

    // sessions
    private String standardLoginPath = "/login";
    private String secureLoginPath = "/securelogin";
    private String googleLoginPath = "/googlelogin";
    private String incommonLoginPath = "/incommonlogin";
    private long standardLoginSec = 9 * 60 * 60; // 9 hour session lifetime
    private long secureLoginSec = 1 * 60 * 60; // 1 hour session lifetime
    private String googleIdentityProvider = "https://idp.u.washington.edu/google";
    private String spRegistryUrl = "https://iam-tools.u.washington.edu/spreg/";

    private String myEntityId = null;
    private String eppnName = "eppn"; // env var name of user eppn

    // key for crypt ops
    private static String cryptKey;

    class RPSession {
        private String viewType;
        private String remoteUser;
        private String rootPath;
        private String servletPath;
        private String pageType;
        private String pageTitle;
        private long ifMatch;
        private long ifNoneMatch;
        private String errorCode;
        private String errorText;
        private boolean isBrowser;
        private String xsrfCode;
        private String remoteAddr;
        private String loginMethod;
        private boolean isAdmin;
        private boolean authn2;
        private boolean isUWLogin;
        private String userIdProvider;
        private String userDisplayName;
        private ModelAndView mv;
        private long timeLeft;
        private boolean isProxy;
        private List altNames;
        private boolean adminRole;
        private boolean isMobile;
    }

    /* send user to login chooser page */
    private ModelAndView loginChooserMV(RPSession session, HttpServletRequest request,
            HttpServletResponse response) {

        String rp = "";
        if (request.getPathInfo() != null)
            rp = request.getPathInfo();
        String rqs = "";
        if (request.getQueryString() != null)
            rqs = "?" + request.getQueryString();
        String red = browserRootPath + request.getServletPath() + rp + rqs;
        log.debug("no user yet: final path=" + red);

        String view = "browser";
        if (session.isMobile)
            view = "mobile";
        ModelAndView mv = new ModelAndView(view + "/chooser");
        mv.addObject("root", browserRootPath);
        mv.addObject("vers", request.getServletPath());
        mv.addObject("pathextra", rp + rqs);
        mv.addObject("uwloginpath", standardLoginPath);
        mv.addObject("googleloginpath", googleLoginPath);
        mv.addObject("incommonloginpath", incommonLoginPath);
        return (mv);
    }

    private RPSession processRequestInfo(HttpServletRequest request, HttpServletResponse response) {
        return processRequestInfo(request, response, true);
    }

    private RPSession processRequestInfo(HttpServletRequest request, HttpServletResponse response,
            boolean canLogin) {
        RPSession session = new RPSession();
        session.isAdmin = false;
        session.adminRole = false;
        session.isUWLogin = false;
        session.isProxy = false;
        String reloginPath = null;

        log.info("RP new session =============== path=" + request.getPathInfo());

        session.isMobile = false;
        Device currentDevice = DeviceUtils.getCurrentDevice(request);
        if (currentDevice != null)
            session.isMobile = currentDevice.isMobile();
        log.debug("mobile? " + session.isMobile);

        // see if logged in (browser has login cookie; cert user has cert)

        int resetAdmin = 1; // on expired or no cookie, reset the 'admin role cookei'
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (int i = 0; i < cookies.length; i++) {
                if (cookies[i].getName().equals(loginCookie)) {
                    log.debug("got cookie " + cookies[i].getName());
                    String cookieStr = RPCrypt.decode(cookies[i].getValue());
                    if (cookieStr == null)
                        continue;
                    String[] cookieData = cookieStr.split(";");
                    if (cookieData.length == 5) {

                        if (cookieData[3].charAt(0) == '2')
                            session.authn2 = true;

                        log.debug("login time = " + cookieData[4]);
                        long cSec = new Long(cookieData[4]);
                        long nSec = new Date().getTime() / 1000;
                        if (cookieData[1].indexOf("@") < 0)
                            session.isUWLogin = true; // klugey way to know UW people
                        session.timeLeft = (cSec + standardLoginSec) - nSec;
                        if (session.timeLeft > 0) {
                            if ((nSec > (cSec + secureLoginSec)) && session.authn2) {
                                log.debug("secure expired");
                                session.authn2 = false;
                                resetAdmin = 2;
                            }

                            // cookie OK
                            session.remoteUser = cookieData[1];
                            session.xsrfCode = cookieData[2];
                            log.debug("login for " + session.remoteUser);
                            if (session.authn2)
                                log.debug("secure login");
                            if (adminGroup.isMember(session.remoteUser)) {
                                log.debug("is admin");
                                session.isAdmin = true;
                            }

                            if (resetAdmin == 1)
                                resetAdmin = 0;
                        } else {
                            log.debug("cookie expired for " + cookieData[1]);
                            // remember where they logged in last
                            if (session.isUWLogin)
                                reloginPath = browserRootPath + request.getServletPath() + standardLoginPath;
                            else if (cookieData[1].indexOf("gmail.com") > 0)
                                reloginPath = browserRootPath + request.getServletPath() + googleLoginPath;
                            // let others choose
                        }
                    }
                } else if (cookies[i].getName().equals(roleCookie) && cookies[i].getValue().equals("a")) {
                    log.debug("got role=admin cookie");
                    session.adminRole = true;
                }
            }
        }

        if (resetAdmin > 0) {
            log.debug("clearing expired admn request");
            session.adminRole = false;
            Cookie c = new Cookie(roleCookie, "x");
            c.setSecure(true);
            c.setPath("/");
            response.addCookie(c);
        }

        if (session.remoteUser != null) {
            // ok, is a logged in browser
            session.viewType = "browser";
            session.isBrowser = true;
            session.rootPath = browserRootPath;

        } else {
            // maybe is cert client
            // use the CN portion of the DN as the client userid
            X509Certificate[] certs = (X509Certificate[]) request
                    .getAttribute("javax.servlet.request.X509Certificate");
            if (certs != null) {
                session.viewType = "xml";
                session.isBrowser = false;
                session.rootPath = certRootPath;
                X509Certificate cert = certs[0];
                String dn = cert.getSubjectX500Principal().getName();
                session.remoteUser = dn.replaceAll(".*CN=", "").replaceAll(",.*", "");
                log.info(".. remote user by cert, dn=" + dn + ", cn=" + session.remoteUser);
                session.altNames = new Vector();
                try {
                    Collection altNames = cert.getSubjectAlternativeNames();
                    if (altNames != null) {
                        for (Iterator i = altNames.iterator(); i.hasNext();) {
                            List item = (List) i.next();
                            Integer type = (Integer) item.get(0);
                            if (type.intValue() == 2) {
                                String altName = (String) item.get(1);
                                log.info(".. adding altname " + altName);
                                session.altNames.add(altName);
                            }
                        }
                    } else
                        session.altNames.add(session.remoteUser); // rules say cn meaningful only when altnames not present
                } catch (CertificateParsingException e) {
                    log.info(".. altname parse failed: " + e);
                }
            }

        }

        /* send missing remoteUser to login */

        if (session.remoteUser == null) {
            if (canLogin) {
                if (reloginPath != null) {
                    log.debug("no user yet:  relogin at " + reloginPath);
                    try {
                        response.sendRedirect(reloginPath);
                    } catch (IOException e) {
                        log.error("redirect: " + e);
                    }
                }
                log.debug("no user yet:  send to choose");
                session.mv = loginChooserMV(session, request, response);
                return session;
            }
            return null;
        }

        // only admins can get admin role
        if (!session.isAdmin)
            session.adminRole = false;
        if (session.adminRole && !session.authn2) { // admin needs 2f
            log.debug("need secure login for admin role");
            sendToLogin(request, response, secureLoginPath);
        }
        session.servletPath = request.getServletPath();
        session.remoteAddr = request.getRemoteAddr();

        // etag headers
        session.ifMatch = getLongHeader(request, "If-Match");
        session.ifNoneMatch = getLongHeader(request, "If-None-Match");
        log.info("tags: match=" + session.ifMatch + ", nonematch=" + session.ifNoneMatch);

        log.info("user: " + session.remoteUser);
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, max_age=1");
        response.setHeader("X-UA-Compatible", "IE=7");

        log.info("user: " + session.remoteUser);
        if (session.viewType.equals("browser") && session.isMobile)
            session.viewType = "mobile";
        return session;
    }

    private String fixPathName(String start, HttpServletRequest request) {
        String path = request.getPathInfo();
        // log.debug("full path = " + path);
        path = path.substring(start.length());
        // log.debug("trunc path = " + path);
        int slash = path.indexOf("/");
        if (slash > 0)
            path = path.substring(0, slash);
        // log.debug("fixed path = " + path);
        return path;
    }

    /* send user to login page */
    private void sendToLogin(HttpServletRequest request, HttpServletResponse response, String loginPath) {

        // delete any existing sessions first
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (int i = 0; i < cookies.length; i++) {
                if (cookies[i].getName().startsWith("_shib")) {
                    log.debug("clearing cookie " + cookies[i].getName());
                    Cookie c = new Cookie(cookies[i].getName(), "");
                    c.setSecure(true);
                    c.setPath("/");
                    c.setMaxAge(0);
                    response.addCookie(c);
                }
            }
        }

        String rp = "";
        if (request.getPathInfo() != null)
            rp = request.getPathInfo();
        String rqs = "";
        if (request.getQueryString() != null)
            rqs = "?" + request.getQueryString();
        String red = browserRootPath + request.getServletPath() + loginPath + rp + rqs;
        log.debug("no user yet: redirect for login to " + red);
        try {
            response.sendRedirect(red);
        } catch (IOException e) {
            log.error("redirect: " + e);
        }
    }

    // create basic model and view
    private ModelAndView basicModelAndView(RPSession session, String view, String basePage) {
        ModelAndView mv = new ModelAndView(view + "/" + basePage);
        mv.addObject("XMLHelper", XMLHelper.class);
        mv.addObject("remote_user", session.remoteUser);
        mv.addObject("root", session.rootPath);
        mv.addObject("vers", session.servletPath);
        if (session.pageType != null)
            mv.addObject("pageType", view + "/" + session.pageType);
        mv.addObject("pageTitle", session.pageTitle);
        mv.addObject("xsrf", session.xsrfCode);
        mv.addObject("timeLeft", session.timeLeft);
        mv.addObject("adminRole", session.adminRole);
        return mv;
    }

    private ModelAndView basicModelAndView(RPSession session) {
        return (basicModelAndView(session, session.viewType, "page"));
    }

    private ModelAndView basicModelAndView(RPSession session, String view) {
        return (basicModelAndView(session, view, "page"));
    }

    // create 'empty' model and view
    private ModelAndView emptyMV(String message, String alert) {
        ModelAndView mv = new ModelAndView("empty");
        if (message != null)
            mv.addObject("msg", message);
        if (message != null)
            mv.addObject("alert", alert);
        return mv;
    }

    private ModelAndView emptyMV(String msg) {
        return emptyMV(msg, null);
    }

    private ModelAndView emptyMV() {
        return emptyMV("session error");
    }

    /*
     * Process login page.
     * Set a cookie and redirect back to original request
     * Encode remoteuser, method and time into the login cookie
     *
     * method = 0 -> google
     * method = 1 -> incommon shib
     * method = 2 -> 2-factor uw shib
     */

    private ModelAndView loginPage(HttpServletRequest request, HttpServletResponse response, int method) {
        String remoteUser = request.getRemoteUser();
        if (remoteUser == null && method == 0) { // social login
            String idp = (String) request.getAttribute("Shib-Identity-Provider");
            String mail = (String) request.getAttribute("mail");
            log.info("social login from " + idp + ", email = " + mail);
            if (idp.equals(googleIdentityProvider)) {
                remoteUser = mail;
            } else {
                log.debug("invalid social login");
                return emptyMV("invalid social login");
            }
        }

        String methodKey = "P";
        if (method == 2)
            methodKey = "2";
        String aclass = (String) request.getAttribute("Shib-AuthnContext-Class");
        if (aclass != null && aclass.equals(SECURE_LOGIN_CLASS))
            methodKey = "2";
        log.debug("method = " + method + ", key = " + methodKey);

        if (remoteUser != null) {
            if (remoteUser.endsWith("@washington.edu")) {
                remoteUser = remoteUser.substring(0, remoteUser.lastIndexOf("@washington.edu"));
                log.info("dropped @washington.edu to get id = " + remoteUser);
            }

            if (remoteUser.endsWith("@uw.edu")) {
                // no longer allow google's @uw to be same as UW login
                // remoteUser = remoteUser.substring(0, remoteUser.lastIndexOf("@uw.edu"));
                // log.info("dropped @uw.edu to get id = " + remoteUser);
                ////return loginChooserMV(session, request, response);  // return to login chooser
                // until we can report some misuse
                return emptyMV("invalid social login");
            }

            double dbl = Math.random();
            long modtime = new Date().getTime(); // milliseconds
            log.debug("login: ck = ...;" + remoteUser + ";" + dbl + ";" + methodKey + ";" + modtime / 1000);
            String enc = RPCrypt.encode(Double.toString(modtime) + ";" + remoteUser + ";" + dbl + ";" + methodKey
                    + ";" + modtime / 1000);
            log.debug("login: enc = " + enc);
            Cookie c = new Cookie(loginCookie, enc);
            c.setSecure(true);
            c.setPath("/");
            response.addCookie(c);
            try {
                String rp = request.getPathInfo();
                int sp = rp.indexOf("/", 2);
                log.debug("in path = " + rp);
                String red = browserRootPath + request.getServletPath();
                if (sp > 1)
                    red = red + rp.substring(sp);
                if (request.getQueryString() != null)
                    red = red + "?" + request.getQueryString();
                log.debug("logon ok, return to " + red);
                response.sendRedirect(red);
            } catch (IOException e) {
                log.error("redirect: " + e);
                return emptyMV("redirect error");
            }
        } else {
            // send login failed message
            ModelAndView mv = new ModelAndView("browser/nologin");
            mv.addObject("root", browserRootPath);
            mv.addObject("vers", request.getServletPath());
            mv.addObject("pageTitle", "login failed");
            mv.addObject("myEntityId", myEntityId);
            return mv;
        }
        return emptyMV();
    }

    @RequestMapping(value = "/login/**", method = RequestMethod.GET)
    public ModelAndView basicLoginPage(HttpServletRequest request, HttpServletResponse response) {
        return loginPage(request, response, 1);
    }

    @RequestMapping(value = "/securelogin/**", method = RequestMethod.GET)
    public ModelAndView secureLoginPage(HttpServletRequest request, HttpServletResponse response) {
        return loginPage(request, response, 2);
    }

    @RequestMapping(value = "/googlelogin/**", method = RequestMethod.GET)
    public ModelAndView googleLoginPage(HttpServletRequest request, HttpServletResponse response) {
        return loginPage(request, response, 0);
    }

    @RequestMapping(value = "/incommonlogin/**", method = RequestMethod.GET)
    public ModelAndView incommonLoginPage(HttpServletRequest request, HttpServletResponse response) {
        return loginPage(request, response, 1);
    }

    /*
     * Process logoutt page
     * Clear cookies, redirect to shib logout
     */

    @RequestMapping(value = "/logout/**", method = RequestMethod.GET)
    public ModelAndView logoutPage(HttpServletRequest request, HttpServletResponse response) {
        // clear cookies
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (int i = 0; i < cookies.length; i++) {
                String ckName = cookies[i].getName();
                if (ckName.equals(loginCookie) || ckName.startsWith("_shib")) {
                    log.debug("cookie to clear " + ckName);
                    Cookie c = new Cookie(ckName, "void");
                    c.setSecure(true);
                    c.setPath("/");
                    c.setMaxAge(0);
                    response.addCookie(c);
                }
            }
        }
        /**
                try {
                   log.debug("redirect to: " +  logoutUrl);
                   response.sendRedirect(logoutUrl);
                } catch (IOException e) {
                   log.error("redirect: " + e);
                }
                return emptyMV("configuration error");
         **/
        String view = "browser";
        Device currentDevice = DeviceUtils.getCurrentDevice(request);
        if (currentDevice != null && currentDevice.isMobile())
            view = "mobile";
        ModelAndView mv = new ModelAndView(view + "/chooser");
        mv.addObject("root", browserRootPath);
        mv.addObject("vers", request.getServletPath());
        mv.addObject("pagetype", "browser/loggedout");
        mv.addObject("pathextra", "");
        mv.addObject("uwloginpath", standardLoginPath);
        mv.addObject("googleloginpath", googleLoginPath);
        mv.addObject("incommonloginpath", incommonLoginPath);
        return (mv);
    }

    // show main page
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView homePage(HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response);
        if (session == null)
            return (emptyMV());
        if (session.mv != null)
            return (session.mv);
        log.info("/ view");
        log.info(".. path=" + request.getPathInfo());

        session.pageTitle = "SP registry home";
        session.pageType = "home";

        ModelAndView mv = basicModelAndView(session);
        mv.addObject("isAdmin", session.isAdmin);
        mv.addObject("isProxy", session.isProxy);

        return (mv);
    }

    // home pages (if 'v1' alone)
    @RequestMapping(value = "/v1", method = RequestMethod.GET)
    public ModelAndView homePageV1(HttpServletRequest request, HttpServletResponse response) {
        log.info("v1 view");
        return homePage(request, response);
    }

    // show rp list page
    @RequestMapping(value = "/rps", method = RequestMethod.GET)
    public ModelAndView getRelyingParties(@RequestParam(value = "selectrp", required = false) String selRp,
            @RequestParam(value = "selecttype", required = false) String selType, HttpServletRequest request,
            HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response, false);
        if (session == null) {
            response.setStatus(418);
            return (emptyMV());
        }
        if (session.mv != null)
            return (session.mv);

        List<RelyingPartyEntry> relyingPartyIds = null;

        if (selType != null && selType.equalsIgnoreCase("all"))
            selType = null;
        relyingPartyIds = rpManager.searchRelyingPartyIds(selRp, selType);
        log.info("found " + relyingPartyIds.size() + " rps");

        //List<Metadata> metadata = rpManager.getMetadata();
        //log.info("found " + metadata.size() + " mds" );

        ModelAndView mv = basicModelAndView(session, "json", "rps");
        mv.addObject("selectrp", selRp == null ? "" : selRp);
        mv.addObject("selecttype", selType == null ? "all" : selType);
        mv.addObject("relyingParties", relyingPartyIds);
        //mv.addObject("metadata", metadata);

        return (mv);
    }

    // specific party
    @RequestMapping(value = "/rp", method = RequestMethod.GET)
    public ModelAndView getRelyingParty(@RequestParam(value = "id", required = true) String id,
            @RequestParam(value = "mdid", required = true) String mdid,
            @RequestParam(value = "view", required = false) String view,
            @RequestParam(value = "dns", required = false) String dns,
            @RequestParam(value = "role", required = false) String role, HttpServletRequest request,
            HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response);
        if (session == null)
            return (emptyMV());
        if (session.mv != null)
            return (session.mv);

        session.pageType = "rp";
        session.pageTitle = "Service provider";

        RelyingParty rp = null;
        RelyingParty rrp = null;

        boolean canEdit = false;

        String errmsg = null;

        ModelAndView mv = basicModelAndView(session, "browser", "rp");

        try {
            rp = rpManager.getRelyingPartyById(id, mdid);
        } catch (RelyingPartyException e) {
            return emptyMV("not found");
        }
        session.pageTitle = rp.getEntityId();
        try {
            if (userCanEdit(session, id)) {
                log.debug("user can edit");
                canEdit = true;
            }
        } catch (DNSVerifyException e) {
            mv.addObject("alert", "Could not verify ownership:\n" + e.getCause());
            response.setStatus(500);
            return mv;
        }

        log.info("returning rp id=" + id);
        List<FilterPolicyGroup> filterPolicyGroups = filterPolicyManager.getFilterPolicyGroups();
        List<Attribute> attributes = filterPolicyManager.getAttributes(rp);

        Proxy proxy = proxyManager.getProxy(id);

        mv.addObject("canEdit", canEdit);
        mv.addObject("relyingParty", rp);
        mv.addObject("filterPolicyGroups", filterPolicyGroups);
        mv.addObject("filterPolicyManager", filterPolicyManager);
        mv.addObject("attributes", attributes);
        mv.addObject("relyingPartyId", id);
        mv.addObject("proxy", proxy);
        mv.addObject("isAdmin", session.isAdmin);
        mv.addObject("isProxy", session.isProxy);
        mv.addObject("dateFormatter", new SimpleDateFormat("yy/MM/dd"));
        return (mv);
    }

    // rp's metadata (API endpoint)
    @RequestMapping(value = "/ws/metadata", method = RequestMethod.GET)
    public ModelAndView getRelyingParty(@RequestParam(value = "id", required = true) String id,
            @RequestParam(value = "mdid", required = true) String mdid, HttpServletRequest request,
            HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response);
        if (session == null)
            return (emptyMV());
        if (session.mv != null)
            return (session.mv);

        session.pageType = "metadata";
        session.pageTitle = "Service provider";

        RelyingParty rp = null;

        String errmsg = null;

        ModelAndView mv = basicModelAndView(session, "xml", "metadata");

        try {
            rp = rpManager.getRelyingPartyById(id, mdid);
        } catch (RelyingPartyException e) {
            response.setStatus(404);
            return emptyMV("not found");
        }
        log.info("returning metadata id=" + id);
        try {
            StringWriter writer = new StringWriter();
            BufferedWriter xout = new BufferedWriter(writer);
            rp.writeXml(xout);
            xout.close();
            mv.addObject("metadata", writer.toString());
        } catch (IOException e) {
            log.error("string writer errro: " + e);
            response.setStatus(500);
            return emptyMV("internal error");
        }

        return (mv);
    }

    // new rp
    @RequestMapping(value = "/new", method = RequestMethod.GET)
    public ModelAndView getRelyingPartyNew(@RequestParam(value = "rpid", required = true) String rpid,
            @RequestParam(value = "mdid", required = false) String mdid,
            @RequestParam(value = "view", required = false) String view,
            @RequestParam(value = "role", required = false) String role,
            @RequestParam(value = "nolook", required = false) String nolook, // enter manual data for this entity
            HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response);
        if (session == null)
            return (emptyMV());
        if (session.mv != null)
            return (session.mv);

        session.pageType = "rp";
        session.pageTitle = "New service provider";
        boolean lookup = true;
        if (nolook != null && nolook.startsWith("y"))
            lookup = false;

        String dns = dnsFromEntityId(rpid);

        if (dns.length() == 0)
            return (emptyMV());

        RelyingParty rp = null;
        boolean canEdit = true;
        String errmsg = null;
        ModelAndView mv = basicModelAndView(session, "browser", "rp");

        // check access
        try {
            if (userCanEdit(session, dns)) {
                log.debug("user owns dns");
            } else {
                // response.setStatus(200);  // 403
                return emptyMV("No permission for " + rpid);
            }
        } catch (DNSVerifyException e) {
            // mv.addObject("alert", "Could not verify ownership:\n" + e.getCause());
            // response.setStatus(500);
            return emptyMV("Could not verify ownership:\n" + e.getCause());
        }

        // check that it doesn't already exist
        try {
            rpManager.getRelyingPartyById(rpid, "UW");
            log.debug("wants new, but already exists");
            return emptyMV("Entity " + rpid + " already exists in the UW federation metadata");
        } catch (RelyingPartyException e) {
            log.debug("really new");
        }

        mv.addObject("newEntity", true);

        if (lookup) {
            mv.addObject("newByLookup", true);
            try {
                rp = rpManager.genRelyingPartyByLookup(hostPortFromEntityId(rpid));
                if (rp != null)
                    log.debug("rp: " + rp.getEntityId());
                try {
                    RelyingParty orp = rpManager.getRelyingPartyById(rp.getEntityId(), "UW");
                    if (orp != null)
                        rp = orp;
                } catch (RelyingPartyException e) {
                    log.debug("rp doesn't already exist in our metadata");
                }

                if (!hostPortFromEntityId(rpid).equals(hostPortFromEntityId(rp.getEntityId()))) {
                    log.info(String.format("requested dns '%s' not equal to fetched entityId '%s'",
                            hostPortFromEntityId(rpid), rp.getEntityId()));
                    return emptyMV(
                            "The entityID you supplied appears to be on a host already registered with a different entityID. "
                                    + "Shibboleth can support multiple entityIDs on one SP, but in most cases that isn't the best approach. "
                                    + "If you actually do need to register additional entityIDs for an existing SP, "
                                    + "you'll need to use the manual registration process.");
                }

                mv.addObject("relyingParty", rp);
                mv.addObject("relyingPartyId", rp.getEntityId());
                session.pageTitle = rp.getEntityId();
            } catch (RelyingPartyException e) {
                mv.addObject("rpnotfound", true);
                log.debug("metadata not found for " + dns);
                log.debug(e.getMessage());
            }
        }
        if (rp == null) {
            if (lookup)
                return emptyMV(rpid + " did not respond with metadata");
            rp = rpManager.genRelyingPartyByName(rpid, dns);
            mv.addObject("relyingParty", rp);
            mv.addObject("relyingPartyId", rpid);
            session.pageTitle = rpid;
        }

        List<FilterPolicyGroup> filterPolicyGroups = filterPolicyManager.getFilterPolicyGroups();
        List<Attribute> attributes = filterPolicyManager.getAttributes(rp);
        mv.addObject("filterPolicyGroups", filterPolicyGroups);
        mv.addObject("filterPolicyManager", filterPolicyManager);
        return (mv);
    }

    // update an rp metadata
    @RequestMapping(value = "/rp", method = RequestMethod.PUT)
    public ModelAndView putRelyingParty(@RequestParam(value = "id", required = true) String id,
            @RequestParam(value = "mdid", required = true) String mdid,
            @RequestParam(value = "role", required = false) String role,
            @RequestParam(value = "xsrf", required = false) String paramXsrf, InputStream in,
            HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response, false);
        if (session == null)
            return (emptyMV());

        log.info("PUT update for: " + id);
        int status = 200;

        if (session.isBrowser && !(paramXsrf != null && paramXsrf.equals(session.xsrfCode))) {
            log.info("got invalid xsrf=" + paramXsrf + ", expected+" + session.xsrfCode);
            return emptyMV("invalid session (xsrf)");
        }

        ModelAndView mv = emptyMV("OK dokey");

        try {
            if (!userCanEdit(session, id)) {
                status = 401;
                mv.addObject("alert", "You are not an owner of that entity.");
                response.setStatus(status);
                return mv;
            }
        } catch (DNSVerifyException e) {
            mv.addObject("alert", "Could not verify ownership:\n" + e.getCause());
            response.setStatus(500);
            return mv;
        }

        RelyingParty relyingParty = null;
        try {
            Document doc = null;
            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = builderFactory.newDocumentBuilder();
            doc = builder.parse(in);
            relyingParty = new RelyingParty(doc.getDocumentElement(), mdid, rpManager.isMetadataEditable(mdid));
        } catch (Exception e) {
            log.info("parse error: " + e);
            status = 400;
            mv.addObject("alert", "The posted document was not valid:\n" + e);
            response.setStatus(status);
            return mv;
        }

        try {
            status = rpManager.updateRelyingParty(relyingParty, mdid);
        } catch (RelyingPartyException e) {
            status = 400;
            mv.addObject("alert", "Update failed:\n" + e.getMessage());
            response.setStatus(status);
            return mv;
        }

        SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
        msg.setTo(mailTo);
        String act = "updated";
        if (status == 201)
            act = "created";
        else if (status >= 400)
            act = "attempted edit of";
        msg.setSubject("Service provider metadata " + act + " by " + session.remoteUser);
        msg.setText("User '" + session.remoteUser + "' " + act + " metadata for '" + id + "'.\nRequest status: "
                + status + "\n\nThis message is advisory.  No response is indicated.");
        try {
            this.mailSender.send(msg);
        } catch (MailException ex) {
            log.error("sending mail: " + ex.getMessage());
        }

        response.setStatus(status);
        return mv;
    }

    // API update an rp metadata
    @RequestMapping(value = "/ws/metadata", method = RequestMethod.PUT)
    public ModelAndView putRelyingParty(@RequestParam(value = "id", required = true) String id,
            @RequestParam(value = "mdid", required = true) String mdid, InputStream in, HttpServletRequest request,
            HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response, false);
        if (session == null)
            return (emptyMV());
        log.info("API PUT update for: " + id);
        int status = 403;

        ModelAndView mv = basicModelAndView(session, "xml", "empty");

        String dns = dnsFromEntityId(id);
        for (int i = 0; i < session.altNames.size(); i++) {
            if (dns.equals(session.altNames.get(i))) {
                log.info("dns match found for " + dns);
                status = 200;
            }
        }
        if (status == 403) {
            mv.addObject("alert", "You are not an owner of that entity.");
            response.setStatus(status);
            return mv;
        }
        if (!rpManager.isMetadataEditable(mdid)) {
            status = 400;
            mv.addObject("alert", "The metadata was not found or is not editable");
            response.setStatus(status);
            return mv;
        }

        RelyingParty rp = null;
        try {
            Document doc = null;
            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = builderFactory.newDocumentBuilder();
            doc = builder.parse(in);
            rp = new RelyingParty(doc.getDocumentElement(), mdid, rpManager.isMetadataEditable(mdid));
        } catch (Exception e) {
            log.info("parse error: " + e);
            status = 400;
            mv.addObject("alert", "The posted document was not valid:\n" + e);
            response.setStatus(status);
            return mv;
        }
        if (rp.getEntityId().equals(id)) {
            try {
                rpManager.updateRelyingParty(rp, mdid);
            } catch (RelyingPartyException e) {
                status = 400;
                mv.addObject("alert", "Update of the metadata failed:\n" + e);
            }
        } else {
            mv.addObject("alert", String.format("Id %s doesn't match %s", rp.getEntityId(), id));
        }

        SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
        msg.setTo(mailTo);
        String act = "updated";
        if (status == 201)
            act = "created";
        else if (status >= 400)
            act = "attempted edit of";
        msg.setSubject("Service provider metadata " + act + " by " + session.remoteUser);
        msg.setText("User '" + session.remoteUser + "' " + act + " metadata for '" + id + "'.\nRequest status: "
                + status + "\n\nThis message is advisory.  No response is indicated.");
        try {
            this.mailSender.send(msg);
        } catch (MailException ex) {
            log.error("sending mail: " + ex.getMessage());
        }

        response.setStatus(status);
        return mv;
    }

    // delete an rp
    @RequestMapping(value = "/rp", method = RequestMethod.DELETE)
    public ModelAndView deleteRelyingParty(@RequestParam(value = "id", required = true) String id,
            @RequestParam(value = "mdid", required = true) String mdid,
            @RequestParam(value = "role", required = false) String role,
            @RequestParam(value = "xsrf", required = false) String paramXsrf, InputStream in,
            HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response, false);
        if (session == null)
            return (emptyMV());

        log.info("DELETE for: " + id);
        int status = 200;

        if (session.isBrowser && !(paramXsrf != null && paramXsrf.equals(session.xsrfCode))) {
            log.info("got invalid xsrf=" + paramXsrf + ", expected+" + session.xsrfCode);
            return emptyMV("invalid session (xsrf)");
        }

        ModelAndView mv = emptyMV("OK dokey delete rp");

        try {
            if (!userCanEdit(session, id)) {
                status = 401;
                mv.addObject("alert", "You are not the owner.");
            } else {
                status = proxyManager.removeRelyingParty(id);
                status = filterPolicyManager.removeEditableRelyingParty(id);
                status = rpManager.removeRelyingParty(id, mdid);
            }
        } catch (FilterPolicyException e) {
            mv.addObject("alert", "delete of filter policy failed:\n" + e.getCause());
            response.setStatus(500);
            return mv;
        } catch (DNSVerifyException e) {
            mv.addObject("alert", "Could not verify ownership:\n" + e.getCause());
            response.setStatus(500);
            return mv;
        }
        SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
        msg.setTo(mailTo);
        msg.setText(
                "User '" + session.remoteUser + "' deleted metadata for '" + id + "'.\nRequest status: " + status);
        try {
            this.mailSender.send(msg);
        } catch (MailException ex) {
            log.error("sending mail: " + ex.getMessage());
        }

        response.setStatus(status);
        return mv;
    }

    // rp attributes 
    @RequestMapping(value = "/rp/attr", method = RequestMethod.GET)
    public ModelAndView getRelyingPartyAttributes(@RequestParam(value = "id", required = true) String id,
            @RequestParam(value = "mdid", required = false) String mdid,
            @RequestParam(value = "view", required = false) String view, HttpServletRequest request,
            HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response);
        if (session == null)
            return (emptyMV());
        if (session.mv != null)
            return (session.mv);

        session.pageType = "relying-party-attr";
        session.pageTitle = "Service provider";
        if (view == null)
            view = "";
        else if (view.equals("edit"))
            session.pageType = "relying-party-attr-edit";

        RelyingParty rp = null;
        String errmsg = null;

        try {
            rp = rpManager.getRelyingPartyById(id, mdid);
        } catch (RelyingPartyException e) {
            return emptyMV("SP not found");
        }
        session.pageTitle = rp.getEntityId();

        List<FilterPolicyGroup> filterPolicyGroups = filterPolicyManager.getFilterPolicyGroups();
        List<Attribute> attributes = filterPolicyManager.getAttributes();

        ModelAndView mv = basicModelAndView(session);

        log.info("returning attrs for id=" + id);

        mv.addObject("relyingPartyId", id);
        mv.addObject("relyingParty", rp);
        mv.addObject("filterPolicyGroups", filterPolicyGroups);
        mv.addObject("filterPolicyManager", filterPolicyManager);
        mv.addObject("remoteUser", session.remoteUser);
        try {
            if (userCanEdit(session, id))
                mv.addObject("domainOwner", true);
        } catch (DNSVerifyException e) {
            mv.addObject("alert", "Could not verify ownership:\n" + e.getCause());
            response.setStatus(500);
            return mv;
        }
        mv.addObject("attributes", attributes);
        return (mv);
    }

    // update an rp's attrs
    @RequestMapping(value = "/rp/attr", method = RequestMethod.PUT)
    public ModelAndView putRelyingPartyAttributes(@RequestParam(value = "id", required = true) String id,
            @RequestParam(value = "policyId", required = true) String policyId,
            @RequestParam(value = "xsrf", required = false) String paramXsrf, InputStream in,
            HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response, false);
        if (session == null)
            return (emptyMV());
        log.info("PUT update attrs for " + id + " in " + policyId);
        int status = 200;

        /**
               if (session.isBrowser && !(paramXsrf!=null && paramXsrf.equals(session.xsrfCode))) {
                   log.info("got invalid xsrf=" + paramXsrf + ", expected+" + session.xsrfCode);
                   return emptyMV("invalid session (xsrf)");
               }
         **/

        ModelAndView mv = emptyMV("OK dokey");

        if (!session.isAdmin) {
            status = 401;
            mv.addObject("alert", "You are not permitted to update attriubtes.");
            response.setStatus(status);
            return mv;
        }

        Document doc = null;
        try {
            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = builderFactory.newDocumentBuilder();
            doc = builder.parse(in);
        } catch (Exception e) {
            log.info("parse error: " + e);
            status = 400;
            mv.addObject("alert", "The posted document was not valid:\n" + e);
        }
        if (doc != null) {
            try {
                filterPolicyManager.updateRelyingParty(policyId, doc);
                status = 200;
            } catch (FilterPolicyException e) {
                status = 400;
                mv.addObject("alert", "Update of the entity failed:" + e);
            }
        }

        SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
        msg.setTo(mailTo);
        String act = "updated";
        if (status == 201)
            act = "created";
        msg.setSubject("Service provider attributes " + act + " by " + session.remoteUser);
        msg.setText("User '" + session.remoteUser + "' " + act + " attributes for '" + id + "'.\nRequest status: "
                + status + "\n");
        try {
            this.mailSender.send(msg);
        } catch (MailException ex) {
            log.error("sending mail: " + ex.getMessage());
        }

        response.setStatus(status);
        return mv;
    }

    // request for attributes
    @RequestMapping(value = "/rp/attrReq", method = RequestMethod.PUT)
    public ModelAndView putRelyingPartyAttrReq(@RequestParam(value = "id", required = true) String id,
            InputStream in, HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response, false);
        if (session == null)
            return (emptyMV());
        log.info("PUT request for: " + id);
        int status = 200;

        ModelAndView mv = emptyMV("OK dokey");

        try {
            if (!userCanEdit(session, id)) {
                status = 401;
                mv.addObject("alert", "You are not an owner of that entity.");
            }
        } catch (DNSVerifyException e) {
            mv.addObject("alert", "Could not verify ownership:\n" + e.getCause());
            response.setStatus(500);
            return mv;
        }

        Document doc = null;
        try {
            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = builderFactory.newDocumentBuilder();
            doc = builder.parse(in);
        } catch (Exception e) {
            log.info("parse error: " + e);
            status = 400;
            mv.addObject("alert", "The posted document was not valid:\n" + e);
        }
        if (doc != null) {
            StringBuffer txt = new StringBuffer(
                    "[ Assign to Identity and Access Management. ]\n\nEntity Id: " + id + "\n");
            txt.append("User:      " + session.remoteUser + "\n\nRequesting:\n");
            List<Element> attrs = XMLHelper.getElementsByName(doc.getDocumentElement(), "Add");
            log.debug(attrs.size() + " adds");
            for (int i = 0; i < attrs.size(); i++)
                txt.append("  Add new attribute: " + attrs.get(i).getAttribute("id") + "\n\n");
            attrs = XMLHelper.getElementsByName(doc.getDocumentElement(), "Drop");
            log.debug(attrs.size() + " drops");
            for (int i = 0; i < attrs.size(); i++)
                txt.append("  Drop existing attribute: " + attrs.get(i).getAttribute("id") + "\n\n");
            Element mele = XMLHelper.getElementByName(doc.getDocumentElement(), "Comments");
            if (mele != null)
                txt.append("\nComment:\n\n" + mele.getTextContent() + "\n\n");
            txt.append("Quick link:\n\n   " + spRegistryUrl + "#a" + id + "\n");

            SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
            /* production to RT system */
            msg.setTo(requestMailTo);
            msg.setSubject("IdP attribute request for " + id);
            msg.setFrom(session.remoteUser + "@uw.edu");
            msg.setText(txt.toString());
            try {
                this.mailSender.send(msg);
            } catch (MailException ex) {
                log.error("sending mail: " + ex.getMessage());
                status = 500;
            }

        }
        response.setStatus(status);
        return mv;
    }

    // all attributes 
    @RequestMapping(value = "/attr", method = RequestMethod.GET)
    public ModelAndView getAttributes(@RequestParam(value = "id", required = false) String id,
            @RequestParam(value = "mdid", required = false) String mdid,
            @RequestParam(value = "view", required = false) String view, HttpServletRequest request,
            HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response);
        if (session == null)
            return (emptyMV());
        if (session.mv != null)
            return (session.mv);

        session.pageType = "attributes";
        session.pageTitle = "Attributes";

        // if (view!=null && view.equals("edit")) session.pageType = "relying-party-attr-edit";
        // RelyingParty rp = null;

        List<Attribute> attributes = filterPolicyManager.getAttributes();

        ModelAndView mv = basicModelAndView(session);

        log.info("returning attrs");

        mv.addObject("attributes", attributes);
        mv.addObject("filterPolicyManager", filterPolicyManager);
        mv.addObject("remoteUser", session.remoteUser);
        return (mv);
    }

    // update an rp's proxy 
    @RequestMapping(value = "/rp/proxy", method = RequestMethod.PUT)
    public ModelAndView putRelyingPartyAttributesZ(@RequestParam(value = "id", required = true) String id,
            @RequestParam(value = "role", required = false) String role,
            @RequestParam(value = "xsrf", required = false) String paramXsrf, InputStream in,
            HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response, false);
        if (session == null)
            return (emptyMV());

        log.info("PUT update proxy for " + id);
        int status = 200;

        if (session.isBrowser && !(paramXsrf != null && paramXsrf.equals(session.xsrfCode))) {
            log.info("got invalid xsrf=" + paramXsrf + ", expected+" + session.xsrfCode);
            return emptyMV("invalid session (xsrf)");
        }

        ModelAndView mv = emptyMV("OK dokey");
        try {
            if (!userCanEdit(session, id)) {
                status = 401;
                mv.addObject("alert", "You are not an owner of that entity.");
                response.setStatus(status);
                return mv;
            }
        } catch (DNSVerifyException e) {
            mv.addObject("alert", "Could not verify ownership:\n" + e.getCause());
            response.setStatus(500);
            return mv;
        }

        Document doc = null;
        try {
            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = builderFactory.newDocumentBuilder();
            doc = builder.parse(in);
        } catch (Exception e) {
            log.info("parse error: " + e);
            status = 400;
            mv.addObject("alert", "The posted document was not valid:\n" + e);
        }
        if (doc != null) {
            try {
                List<Element> eles = XMLHelper.getElementsByName(doc.getDocumentElement(), "Proxy");
                if (eles.size() != 1)
                    throw new ProxyException("proxy xml must contain one element");
                Element pxe = eles.get(0);
                Proxy newproxy = new Proxy(pxe);
                if (!newproxy.getEntityId().equals(id))
                    throw new ProxyException("post doesn't match qs id");
                proxyManager.updateProxy(newproxy);
                status = 200;
            } catch (ProxyException e) {
                status = 400;
                mv.addObject("alert", "Update of the entity failed:" + e);
            }
        }

        SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
        msg.setTo(mailTo);
        String act = "updated";
        if (status == 201)
            act = "created";
        msg.setSubject("Service provider proxy info " + act + " by " + session.remoteUser);
        msg.setText("User '" + session.remoteUser + "' " + act + " proxy info '" + id + "'.\nRequest status: "
                + status + "\n");
        try {
            this.mailSender.send(msg);
        } catch (MailException ex) {
            log.error("sending mail: " + ex.getMessage());
        }

        response.setStatus(status);
        return mv;
    }

    public void setRelyingPartyManager(RelyingPartyManager m) {
        rpManager = m;
    }

    public void setFilterPolicyManager(FilterPolicyManager m) {
        filterPolicyManager = m;
    }

    public void setProxyManager(ProxyManager m) {
        proxyManager = m;
    }

    /* utility */
    private boolean userCanEdit(RPSession session, String entityId) throws DNSVerifyException {
        return session.adminRole || dnsVerifier.isOwner(entityId, session.remoteUser, null);
    }

    private long getLongHeader(HttpServletRequest request, String name) {
        try {
            String hdr = request.getHeader(name);
            if (hdr == null)
                return 0;
            if (hdr.equals("*"))
                return (-1);
            return Long.parseLong(hdr);
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    private String hostPortFromEntityId(String entityId) {
        entityId = entityId.replaceFirst("^https?://", "");
        entityId = entityId.replaceFirst("/.*$", "");
        return entityId;
    }

    private String dnsFromEntityId(String entityid) {
        String dns = entityid;
        if (dns.startsWith("http://"))
            dns = dns.substring(7);
        if (dns.startsWith("https://"))
            dns = dns.substring(8);
        int i = dns.indexOf("/");
        if (i > 0)
            dns = dns.substring(0, i);
        i = dns.indexOf(":");
        if (i > 0)
            dns = dns.substring(0, i);
        return dns;
    }

    private String cleanString(String in) {
        if (in == null)
            return null;
        return in.replaceAll("&", "").replaceAll("<", "").replaceAll(">", "");
    }

    public void setCertRootPath(String path) {
        certRootPath = path;
    }

    public void setBrowserRootPath(String path) {
        browserRootPath = path;
    }

    public void setLoginCookie(String v) {
        loginCookie = v;
    }

    public void setRoleCookie(String v) {
        roleCookie = v;
    }

    public void setLogoutUrl(String v) {
        logoutUrl = v;
    }

    public void setCryptKey(String v) {
        cryptKey = v;
    }

    public void setMailTo(String v) {
        log.debug("mailTo = " + v);
        mailTo = v;
    }

    public void setRequestMailTo(String v) {
        requestMailTo = v;
    }

    public void setAdminGroupName(String v) {
        log.debug("admin group = " + v);
        adminGroupName = v;
    }

    public void setStandardLoginSec(long v) {
        standardLoginSec = v;
    }

    public void setSecureLoginSec(long v) {
        secureLoginSec = v;
    }

    public void setStandardLoginPath(String v) {
        standardLoginPath = v;
    }

    public void setSecureLoginPath(String v) {
        secureLoginPath = v;
    }

    public void setGoogleIdentityProvider(String v) {
        googleIdentityProvider = v;
    }

    public void setMyEntityId(String v) {
        myEntityId = v;
    }

    public void setEppnName(String v) {
        eppnName = v;
    }

    public void setSpRegistryUrl(String v) {
        spRegistryUrl = v;
    }

    public static RelyingPartyManager getRelyingPartyManager() {
        return rpManager;
    }

    /* See if extra login suggested.
     */
    /**
        private boolean needMoreAuthn(GwsGroup group, GwsSession session, HttpServletResponse response) {
           if (group.getSecurityLevel()>1 && !session.authn2) {
       log.debug("update needs 2-factor");
       if (session.isBrowser) response.setStatus(402);
       else response.setStatus(401);
       return true;
           }
           return false;
        }
     **/

    public void init() {
        log.info("RelyingPartyController init");
        RPCrypt.init(cryptKey);
        adminGroup = groupManager.getGroup(adminGroupName);
    }

    /* refresh cache groups  */

    @RequestMapping(value = "/ws/refresh", method = RequestMethod.GET)
    public ModelAndView getSet(HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response);
        response.setStatus(200);
        log.info("refreshing cache groups");
        adminGroup = groupManager.getGroup(adminGroupName);
        return emptyMV();
    }

    /* status  */

    @RequestMapping(value = "/status", method = RequestMethod.GET)
    public ModelAndView gettatus(HttpServletRequest request, HttpServletResponse response) {

        RPSession session = processRequestInfo(request, response);
        response.setStatus(200);
        log.info("status request");
        adminGroup = groupManager.getGroup(adminGroupName);
        return emptyMV();
    }

    // diagnostic
    @RequestMapping(value = "/**", method = RequestMethod.GET)
    public ModelAndView homePageStar(HttpServletRequest request, HttpServletResponse response) {
        log.info("Star view");
        return homePage(request, response);
    }

}