diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml
deleted file mode 100644
index 67d8bd146..000000000
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml
+++ /dev/null
@@ -1,128 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    Copyright 2018 The MIT Internet Trust Consortium
-   
-    Portions copyright 2011-2013 The MITRE Corporation
-   
-    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.
- -->
-<beans xmlns="http://www.springframework.org/schema/beans"
-	   xmlns:jdbc="http://www.springframework.org/schema/jdbc"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
-       						http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd">
-
-	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
-		<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
-		<property name="jdbcUrl" value="jdbc:hsqldb:mem:oic;sql.syntax_mys=true" />
-<!-- 		<property name="jdbcUrl" value="jdbc:hsqldb:file:/tmp/oic;sql.syntax_mys=true" /> -->
-		<property name="username" value="oic" />
-		<property name="password" value="oic" />
-	</bean>
-
-	<!-- Use the following to set up the OIC tables in the in-memory DB
- 		   If you are using a file based HSQLDB you should not run this every time. -->
-	<jdbc:initialize-database data-source="dataSource">
-		<jdbc:script location="classpath:/db/hsql/hsql_database_tables.sql"/>
-		<!-- The following file is for the jdbc-user-service spring security implementation -->
-		<jdbc:script location="classpath:/db/hsql/security-schema.sql"/>
-		<!-- The following files are for safely bootstrapping users and clients into the database -->
-		<jdbc:script location="classpath:/db/hsql/loading_temp_tables.sql"/>
-		<jdbc:script location="classpath:/db/hsql/users.sql"/>
-		<jdbc:script location="classpath:/db/hsql/clients.sql"/>
-		<jdbc:script location="classpath:/db/hsql/scopes.sql"/>
-	</jdbc:initialize-database>
-
-	<bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
- 		<property name="databasePlatform" value="org.eclipse.persistence.platform.database.HSQLPlatform" />
-		<property name="showSql" value="true" />
-	</bean>
-
-	<!--  The following is for connecting to a MySQL database that has been initialized with 
-			src/main/resources/db/mysql/mysql_database_tables.sql -->
-<!-- 	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"> -->
-<!-- 		<property name="driverClassName" value="com.mysql.jdbc.Driver" /> -->
-<!-- 		<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/oic" /> -->
-<!-- 		<property name="username" value="oic" /> -->
-<!-- 		<property name="password" value="oic" /> -->
-<!--  	</bean> -->
-
-<!-- 	<bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter"> -->
-<!-- 		<property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" /> -->
-<!-- 		<property name="showSql" value="true" /> -->
-<!-- 	</bean> -->
-
-	<!-- You can optionally initialize the database with test values here, 
-		but this is not recommended for real systems -->
-<!-- 	<jdbc:initialize-database data-source="dataSource"> -->
-<!-- 		<jdbc:script location="classpath:/db/tables/mysql_database_tables.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/tables/security-schema.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/tables/loading_temp_tables.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/mysql/users.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/mysql/clients.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/mysql/scopes.sql"/> -->
-<!-- 	</jdbc:initialize-database> -->
-
-	<!--  The following is for connecting to a PostgreSQL database that has been initialized with
-			src/main/resources/db/psql/psql_database_tables.sql -->
-	<!--
-	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
-		<property name="driverClassName" value="org.postgresql.Driver" />
-		<property name="jdbcUrl" value="jdbc:postgresql://localhost/oic" />
-		<property name="username" value="oic" />
-		<property name="password" value="oic" />
-	</bean>
-
-
-	<bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
-		<property name="databasePlatform" value="org.eclipse.persistence.platform.database.PostgreSQLPlatform" />
-		<property name="showSql" value="true" />
-	</bean>
-	-->
-	
-	<!-- You can optionally initialize the database with test values here, 
-		but this is not recommended for real systems -->
-<!-- 	<jdbc:initialize-database data-source="dataSource"> -->
-<!-- 		<jdbc:script location="classpath:/db/psql/psql_database_tables.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/psql/security-schema.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/psql/loading_temp_tables.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/psql/users.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/psql/clients.sql"/> -->
-<!-- 		<jdbc:script location="classpath:/db/psql/scopes.sql"/> -->
-<!-- 	</jdbc:initialize-database> -->
-
-	<!--  The following is for connecting to a Oracle database that has been initialized with
-			src/main/resources/db/oracle/oracle_database_tables.sql -->
-	<!--<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
-		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
-		<property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:XE" />
-		<property name="username" value="oic" />
-		<property name="password" value="oic" />
-	</bean>-->
-
-	<!--<bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
-		<property name="databasePlatform" value="org.eclipse.persistence.platform.database.OraclePlatform" />
-		<property name="showSql" value="true" />
-	</bean>-->
-
-	<!-- Use the following to set up the OIC tables in the Oracle DB
-		   Below scripts are intended to be run once at startup. -->
-	<!--<jdbc:initialize-database data-source="dataSource">
-		<jdbc:script location="classpath:/db/oracle/oracle_database_tables.sql"/>
-		<jdbc:script location="classpath:/db/oracle/security-schema_oracle.sql"/>
-		<jdbc:script location="classpath:/db/oracle/loading_temp_tables_oracle.sql"/>
-		<jdbc:script location="classpath:/db/oracle/users_oracle.sql"/>
-		<jdbc:script location="classpath:/db/oracle/clients_oracle.sql"/>
-		<jdbc:script location="classpath:/db/oracle/scopes_oracle.sql"/>
-	</jdbc:initialize-database>-->
-</beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml
deleted file mode 100644
index 544f01c98..000000000
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-    Copyright 2018 The MIT Internet Trust Consortium
-   
-    Portions copyright 2011-2013 The MITRE Corporation
-   
-    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.
- -->
-<beans xmlns="http://www.springframework.org/schema/beans"
-	xmlns:mvc="http://www.springframework.org/schema/mvc"
-	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xmlns:tx="http://www.springframework.org/schema/tx"
-	xmlns:context="http://www.springframework.org/schema/context"
-	xmlns:security="http://www.springframework.org/schema/security"
-	xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
-	xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
-		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
-		http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd
-		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
-		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
-		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
-
-	<bean id="configBean" class="org.mitre.openid.connect.config.ConfigurationPropertiesBean">
-	    
-	    <!-- This property sets the root URL of the server, known as the issuer -->
-		<property name="issuer" value="http://localhost:8080/openid-connect-server-webapp/" />
-		
-		<!-- This property is a URL pointing to a logo image 24px high to be used in the top bar -->
- 		<property name="logoImageUrl" value="resources/images/openid_connect_small.png" />
- 		
- 		<!-- This property sets the display name of the server, displayed in the topbar and page title -->
- 		<property name="topbarTitle" value="OpenID Connect Server" />
- 		
-		<!-- This property sets the lifetime of registration access tokens, in seconds. Leave it unset (null) for no rotation. -->
-		<!-- <property name="regTokenLifeTime" value="172800" /> -->
-		
-		<!-- This property forces the issuer value to start with "https", recommended on production servers -->
-		<!-- <property name="forceHttps" value="true" /> -->
-		
-		<!-- This property sets the locale for server text -->
-		<!-- <property name="locale" value="sv" /> -->
-
-		<!-- This property sets the set of namespaces for language translation files. The default is "messages". These are checked in the order presented here. -->
-		<!-- 
-		<property name="languageNamespaces">
-			<list>
-				<value>foo</value>
-				<value>bar</value>
-				<value>messages</value>
-			</list>
-		</property>
-		-->
-
-		<!-- This property indicates if a dynamically registered client supports dual flows, such as client_credentials
-		at the same time with authorization_code or implicit -->
-		<!-- <property name="dualClient" value="true" /> -->
-		
-		<!-- This property turns on HEART compliance mode -->
-		<!-- <property name="heartMode" value="true" /> -->
-
-        <!-- This property allows the server to create and accept fully-composed 
-        user URIs (with the user-code emebedded) for the device flow -->
-        <!-- <property name="allowCompleteDeviceCodeUri" value="true" /> -->
-
-	</bean>
-	
-</beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag
deleted file mode 100644
index 4b0aa920a..000000000
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag
+++ /dev/null
@@ -1,4 +0,0 @@
-<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
-<c:if test="${ config.heartMode }"><span class="pull-left"><img src="resources/images/heart_mode.png" alt="HEART Mode" title="This server is running in HEART Compliance Mode" /></span> </c:if>
-<spring:message code="copyright" arguments="${project.version}"/>
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java
deleted file mode 100644
index 26055501a..000000000
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*******************************************************************************
- * Copyright 2018 The MIT Internet Trust Consortium
- *
- * 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 org.mitre.openid.connect.web;
-
-import java.text.ParseException;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.mitre.jwt.assertion.AssertionValidator;
-import org.mitre.jwt.assertion.impl.SelfAssertionValidator;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.UserInfoService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
-import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.util.UriComponents;
-import org.springframework.web.util.UriComponentsBuilder;
-import org.springframework.web.util.UriUtils;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.JWTParser;
-
-/**
- * Implementation of the End Session Endpoint from OIDC session management
- * 
- * @author jricher
- *
- */
-@Controller
-public class EndSessionEndpoint {
-
-	public static final String URL = "endsession";
-	
-	private static final String CLIENT_KEY = "client";
-	private static final String STATE_KEY = "state";
-	private static final String REDIRECT_URI_KEY = "redirectUri";
-
-	private static Logger logger = LoggerFactory.getLogger(EndSessionEndpoint.class);
-	
-	@Autowired
-	private SelfAssertionValidator validator;
-	
-	@Autowired
-	private UserInfoService userInfoService;
-	
-	@Autowired
-	private ClientDetailsEntityService clientService;
-	
-	@RequestMapping(value = "/" + URL, method = RequestMethod.GET)
-	public String endSession(@RequestParam (value = "id_token_hint", required = false) String idTokenHint,  
-		    @RequestParam (value = "post_logout_redirect_uri", required = false) String postLogoutRedirectUri,
-		    @RequestParam (value = STATE_KEY, required = false) String state,
-		    HttpServletRequest request,
-		    HttpServletResponse response,
-		    HttpSession session,
-		    Authentication auth, Model m) {
-
-		// conditionally filled variables
-		JWTClaimsSet idTokenClaims = null; // pulled from the parsed and validated ID token
-		ClientDetailsEntity client = null; // pulled from ID token's audience field
-		
-		if (!Strings.isNullOrEmpty(postLogoutRedirectUri)) {
-			session.setAttribute(REDIRECT_URI_KEY, postLogoutRedirectUri);
-		}
-		if (!Strings.isNullOrEmpty(state)) {
-			session.setAttribute(STATE_KEY, state);
-		}
-		
-		// parse the ID token hint to see if it's valid
-		if (!Strings.isNullOrEmpty(idTokenHint)) {
-			try {
-				JWT idToken = JWTParser.parse(idTokenHint);
-				
-				if (validator.isValid(idToken)) {
-					// we issued this ID token, figure out who it's for
-					idTokenClaims = idToken.getJWTClaimsSet();
-					
-					String clientId = Iterables.getOnlyElement(idTokenClaims.getAudience());
-					
-					client = clientService.loadClientByClientId(clientId);
-					
-					// save a reference in the session for us to pick up later
-					//session.setAttribute("endSession_idTokenHint_claims", idTokenClaims);
-					session.setAttribute(CLIENT_KEY, client);
-				}
-			} catch (ParseException e) {
-				// it's not a valid ID token, ignore it
-				logger.debug("Invalid id token hint", e);
-			} catch (InvalidClientException e) {
-				// couldn't find the client, ignore it
-				logger.debug("Invalid client", e);
-			}
-		}
-		
-		// are we logged in or not?
-		if (auth == null || !request.isUserInRole("ROLE_USER")) {
-			// we're not logged in anyway, process the final redirect bits if needed
-			return processLogout(null, request, response, session, auth, m);
-		} else {
-			// we are logged in, need to prompt the user before we log out
-		
-			// see who the current user is
-			UserInfo ui = userInfoService.getByUsername(auth.getName()); 
-			
-			if (idTokenClaims != null) {
-				String subject = idTokenClaims.getSubject();
-				// see if the current user is the same as the one in the ID token
-				// TODO: should we do anything different in these cases?
-				if (!Strings.isNullOrEmpty(subject) && subject.equals(ui.getSub())) {
-					// it's the same user
-				} else { 
-					// it's not the same user
-				}
-			}
-
-			m.addAttribute("client", client);
-			m.addAttribute("idToken", idTokenClaims);
-			
-			// display the log out confirmation page
-			return "logoutConfirmation";
-		}
-	}
-	
-	@RequestMapping(value = "/" + URL, method = RequestMethod.POST)
-	public String processLogout(@RequestParam(value = "approve", required = false) String approved,
-			HttpServletRequest request,
-			HttpServletResponse response,
-		    HttpSession session,
-		    Authentication auth, Model m) {
-
-		String redirectUri = (String) session.getAttribute(REDIRECT_URI_KEY);
-		String state = (String) session.getAttribute(STATE_KEY);
-		ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(CLIENT_KEY);
-		
-		if (!Strings.isNullOrEmpty(approved)) {
-			// use approved, perform the logout
-			if (auth != null){    
-				new SecurityContextLogoutHandler().logout(request, response, auth);
-			}
-			SecurityContextHolder.getContext().setAuthentication(null);
-			// TODO: hook into other logout post-processing
-		}
-		
-		// if the user didn't approve, don't log out but hit the landing page anyway for redirect as needed
-
-		
-		
-		// if we have a client AND the client has post-logout redirect URIs
-		// registered AND the URI given is in that list, then...
-		if (!Strings.isNullOrEmpty(redirectUri) && 
-			client != null && client.getPostLogoutRedirectUris() != null) {
-			
-			if (client.getPostLogoutRedirectUris().contains(redirectUri)) {
-				// TODO: future, add the redirect URI to the model for the display page for an interstitial
-				// m.addAttribute("redirectUri", postLogoutRedirectUri);
-				
-				UriComponents uri = UriComponentsBuilder.fromHttpUrl(redirectUri).queryParam("state", state).build();
-				
-				return "redirect:" + uri;
-			}
-		}
-		
-		// otherwise, return to a nice post-logout landing page
-		return "postLogout";
-	}
-
-}
diff --git a/openid-connect-server-webapp/.gitignore b/perun-oidc-server-webapp/.gitignore
similarity index 100%
rename from openid-connect-server-webapp/.gitignore
rename to perun-oidc-server-webapp/.gitignore
diff --git a/openid-connect-server-webapp/pom.xml b/perun-oidc-server-webapp/pom.xml
similarity index 71%
rename from openid-connect-server-webapp/pom.xml
rename to perun-oidc-server-webapp/pom.xml
index be9e63a13..8163fe1c4 100644
--- a/openid-connect-server-webapp/pom.xml
+++ b/perun-oidc-server-webapp/pom.xml
@@ -19,67 +19,30 @@
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 	<modelVersion>4.0.0</modelVersion>
 	<parent>
-		<groupId>org.mitre</groupId>
-		<artifactId>openid-connect-parent</artifactId>
+		<groupId>cz.muni.ics</groupId>
+		<artifactId>perun-oidc-parent</artifactId>
 		<version>2.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
-	<artifactId>openid-connect-server-webapp</artifactId>
+	<artifactId>perun-oidc-server-webapp</artifactId>
 	<packaging>war</packaging>
-	<name>OpenID Connect Server Webapp</name>
 
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-compiler-plugin</artifactId>
-				<configuration>
-					<source>${java-version}</source>
-					<target>${java-version}</target>
-				</configuration>
-			</plugin>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-war-plugin</artifactId>
-				<configuration>
-					<warName>openid-connect-server-webapp</warName>
-					<webResources>
-						<resource>
-							<directory>src/main/webapp</directory>
-							<filtering>true</filtering>
-							<includes>
-								<include>**/*.tag</include>
-								<include>**/*.jsp</include>
-							</includes>
-						</resource>
-						<resource>
-							<directory>src/main/webapp</directory>
-							<filtering>false</filtering>
-							<excludes>
-								<exclude>**/*.tag</exclude>
-								<exclude>**/*.jsp</exclude>
-							</excludes>
-						</resource>
-					</webResources>
-					<packagingExcludes>less/**</packagingExcludes>
-				</configuration>
-			</plugin>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-dependency-plugin</artifactId>
-				<executions>
-					<execution>
-						<id>install</id>
-						<phase>install</phase>
-						<goals>
-							<goal>sources</goal>
-						</goals>
-					</execution>
-				</executions>
-			</plugin>
-		</plugins>
-	</build>
+	<properties>
+		<config.location>/etc/perun</config.location>
+		<log.to>FILE</log.to>
+		<log.contextName>oidc</log.contextName>
+		<log.facility>LOCAL7</log.facility>
+		<log.level>info</log.level>
+		<!--suppress UnresolvedMavenProperty -->
+		<log.rolling-file>${catalina.base}/logs/${CONTEXT_NAME}</log.rolling-file>
+		<!--suppress UnresolvedMavenProperty -->
+		<log.file>${catalina.base}/logs/${CONTEXT_NAME}</log.file>
+		<log.trace.file-extension>trace</log.trace.file-extension>
+		<log.file-extension>log</log.file-extension>
+		<log.times.file-extension>times</log.times.file-extension>
+		<final.name>oidc</final.name>
+	</properties>
 
 	<dependencies>
 		<dependency>
@@ -112,7 +75,78 @@
 			<groupId>com.zaxxer</groupId>
 			<artifactId>HikariCP</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>cz.muni.ics</groupId>
+			<artifactId>perun-oidc-server</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.security.extensions</groupId>
+			<artifactId>spring-security-saml2-core</artifactId>
+		</dependency>
 	</dependencies>
 
-	<description>Deployable package of the OpenID Connect server</description>
+	<build>
+		<finalName>${final.name}</finalName>
+		<resources>
+			<resource>
+				<directory>src/main/resources</directory>
+				<filtering>true</filtering>
+				<includes>
+					<include>logback.xml</include>
+					<include>**/*</include>
+				</includes>
+			</resource>
+		</resources>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>${java-version}</source>
+					<target>${java-version}</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-war-plugin</artifactId>
+				<configuration>
+					<warName>openid-connect-server-webapp</warName>
+					<webResources>
+						<resource>
+							<directory>src/main/webapp</directory>
+							<filtering>true</filtering>
+							<includes>
+								<include>**/*.tag</include>
+								<include>**/*.jsp</include>
+								<include>WEB-INF/user-context.xml</include>
+							</includes>
+						</resource>
+						<resource>
+							<directory>src/main/webapp</directory>
+							<filtering>false</filtering>
+							<excludes>
+								<exclude>**/*.tag</exclude>
+								<exclude>**/*.jsp</exclude>
+							</excludes>
+						</resource>
+					</webResources>
+					<packagingExcludes>less/**</packagingExcludes>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-dependency-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>install</id>
+						<phase>install</phase>
+						<goals>
+							<goal>sources</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+
 </project>
diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/clients.sql b/perun-oidc-server-webapp/src/main/resources/db/hsql/clients.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/hsql/clients.sql
rename to perun-oidc-server-webapp/src/main/resources/db/hsql/clients.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_index.sql b/perun-oidc-server-webapp/src/main/resources/db/hsql/hsql_database_index.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_index.sql
rename to perun-oidc-server-webapp/src/main/resources/db/hsql/hsql_database_index.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_tables.sql b/perun-oidc-server-webapp/src/main/resources/db/hsql/hsql_database_tables.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_tables.sql
rename to perun-oidc-server-webapp/src/main/resources/db/hsql/hsql_database_tables.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/loading_temp_tables.sql b/perun-oidc-server-webapp/src/main/resources/db/hsql/loading_temp_tables.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/hsql/loading_temp_tables.sql
rename to perun-oidc-server-webapp/src/main/resources/db/hsql/loading_temp_tables.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/scopes.sql b/perun-oidc-server-webapp/src/main/resources/db/hsql/scopes.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/hsql/scopes.sql
rename to perun-oidc-server-webapp/src/main/resources/db/hsql/scopes.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/security-schema.sql b/perun-oidc-server-webapp/src/main/resources/db/hsql/security-schema.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/hsql/security-schema.sql
rename to perun-oidc-server-webapp/src/main/resources/db/hsql/security-schema.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/users.sql b/perun-oidc-server-webapp/src/main/resources/db/hsql/users.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/hsql/users.sql
rename to perun-oidc-server-webapp/src/main/resources/db/hsql/users.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/clients.sql b/perun-oidc-server-webapp/src/main/resources/db/mysql/clients.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/mysql/clients.sql
rename to perun-oidc-server-webapp/src/main/resources/db/mysql/clients.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_index.sql b/perun-oidc-server-webapp/src/main/resources/db/mysql/mysql_database_index.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_index.sql
rename to perun-oidc-server-webapp/src/main/resources/db/mysql/mysql_database_index.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_tables.sql b/perun-oidc-server-webapp/src/main/resources/db/mysql/mysql_database_tables.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_tables.sql
rename to perun-oidc-server-webapp/src/main/resources/db/mysql/mysql_database_tables.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/scopes.sql b/perun-oidc-server-webapp/src/main/resources/db/mysql/scopes.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/mysql/scopes.sql
rename to perun-oidc-server-webapp/src/main/resources/db/mysql/scopes.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/security-schema.sql b/perun-oidc-server-webapp/src/main/resources/db/mysql/security-schema.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/mysql/security-schema.sql
rename to perun-oidc-server-webapp/src/main/resources/db/mysql/security-schema.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/users.sql b/perun-oidc-server-webapp/src/main/resources/db/mysql/users.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/mysql/users.sql
rename to perun-oidc-server-webapp/src/main/resources/db/mysql/users.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/clients_oracle.sql b/perun-oidc-server-webapp/src/main/resources/db/oracle/clients_oracle.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/clients_oracle.sql
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/clients_oracle.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/create_db-user b/perun-oidc-server-webapp/src/main/resources/db/oracle/create_db-user
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/create_db-user
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/create_db-user
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml b/perun-oidc-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml
similarity index 84%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml
index 2aba62824..1578a9de6 100644
--- a/openid-connect-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml
+++ b/perun-oidc-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml
@@ -20,7 +20,7 @@
 
     <description>OpenID Connect Server entities</description>
 
-    <entity class="org.mitre.oauth2.model.AuthenticationHolderEntity" name="AuthenticationHolderEntity">
+    <entity class="cz.muni.ics.oauth2.model.AuthenticationHolderEntity" name="AuthenticationHolderEntity">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -33,7 +33,7 @@
                 <collection-table name="auth_holder_authority">
                     <join-column name="owner_id"/>
                 </collection-table>
-                <convert converter="org.mitre.oauth2.model.convert.SimpleGrantedAuthorityStringConverter"/>
+                <convert converter="cz.muni.ics.oauth2.model.convert.SimpleGrantedAuthorityStringConverter"/>
                 <column name="authority"/>
             </element-collection>
             <!-- table name too long: authentication_holder_resource_id -->
@@ -57,7 +57,7 @@
                 </collection-table>
                 <column name="val"/>
                 <map-key-column name="extension"/>
-                <convert converter="org.mitre.oauth2.model.convert.SerializableStringConverter"/>
+                <convert converter="cz.muni.ics.oauth2.model.convert.SerializableStringConverter"/>
             </element-collection>
             <!-- table name too long: authentication_holder_request_parameter -->
             <element-collection fetch="EAGER" name="requestParameters">
@@ -70,7 +70,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.oauth2.model.AuthorizationCodeEntity" name="AuthorizationCodeEntity">
+    <entity class="cz.muni.ics.oauth2.model.AuthorizationCodeEntity" name="AuthorizationCodeEntity">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -81,7 +81,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.oauth2.model.ClientDetailsEntity" name="ClientDetailsEntity">
+    <entity class="cz.muni.ics.oauth2.model.ClientDetailsEntity" name="ClientDetailsEntity">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -92,27 +92,27 @@
             <!-- column name too long: user_info_encrypted_response_alg -->
             <basic name="userInfoEncryptedResponseAlg">
                 <column name="user_info_encrypted_resp_alg"/>
-                <convert converter="org.mitre.oauth2.model.convert.JWEAlgorithmStringConverter"/>
+                <convert converter="cz.muni.ics.oauth2.model.convert.JWEAlgorithmStringConverter"/>
             </basic>
             <!-- column name too long: user_info_encrypted_response_enc -->
             <basic name="userInfoEncryptedResponseEnc">
                 <column name="user_info_encrypted_resp_enc"/>
-                <convert converter="org.mitre.oauth2.model.convert.JWEEncryptionMethodStringConverter"/>
+                <convert converter="cz.muni.ics.oauth2.model.convert.JWEEncryptionMethodStringConverter"/>
             </basic>
             <!-- column name too long: id_token_encrypted_response_alg -->
             <basic name="idTokenEncryptedResponseAlg">
                 <column name="id_token_encrypted_resp_alg"/>
-                <convert converter="org.mitre.oauth2.model.convert.JWEAlgorithmStringConverter"/>
+                <convert converter="cz.muni.ics.oauth2.model.convert.JWEAlgorithmStringConverter"/>
             </basic>
             <!-- column name too long: id_token_encrypted_response_enc -->
             <basic name="idTokenEncryptedResponseEnc">
                 <column name="id_token_encrypted_resp_enc"/>
-                <convert converter="org.mitre.oauth2.model.convert.JWEEncryptionMethodStringConverter"/>
+                <convert converter="cz.muni.ics.oauth2.model.convert.JWEEncryptionMethodStringConverter"/>
             </basic>
             <!-- column name too long: token_endpoint_auth_signing_alg -->
             <basic name="tokenEndpointAuthSigningAlg">
                 <column name="token_endpoint_auth_sign_alg"/>
-                <convert converter="org.mitre.oauth2.model.convert.JWSAlgorithmStringConverter"/>
+                <convert converter="cz.muni.ics.oauth2.model.convert.JWSAlgorithmStringConverter"/>
             </basic>
             <!-- table name too long: client_post_logout_redirect_uri -->
             <element-collection fetch="EAGER" name="postLogoutRedirectUris">
@@ -124,7 +124,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.oauth2.model.OAuth2AccessTokenEntity" name="OAuth2AccessTokenEntity">
+    <entity class="cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity" name="OAuth2AccessTokenEntity">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -135,7 +135,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.oauth2.model.OAuth2RefreshTokenEntity" name="OAuth2RefreshTokenEntity">
+    <entity class="cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity" name="OAuth2RefreshTokenEntity">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -146,7 +146,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.oauth2.model.SavedUserAuthentication" name="SavedUserAuthentication">
+    <entity class="cz.muni.ics.oauth2.model.SavedUserAuthentication" name="SavedUserAuthentication">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -157,7 +157,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.oauth2.model.SystemScope" name="SystemScope">
+    <entity class="cz.muni.ics.oauth2.model.SystemScope" name="SystemScope">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -168,7 +168,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.openid.connect.model.ApprovedSite" name="ApprovedSite">
+    <entity class="cz.muni.ics.openid.connect.model.ApprovedSite" name="ApprovedSite">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -179,7 +179,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.openid.connect.model.BlacklistedSite" name="BlacklistedSite">
+    <entity class="cz.muni.ics.openid.connect.model.BlacklistedSite" name="BlacklistedSite">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -190,7 +190,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.openid.connect.model.PairwiseIdentifier" name="PairwiseIdentifier">
+    <entity class="cz.muni.ics.openid.connect.model.PairwiseIdentifier" name="PairwiseIdentifier">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -201,7 +201,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.openid.connect.model.WhitelistedSite" name="WhitelistedSite">
+    <entity class="cz.muni.ics.openid.connect.model.WhitelistedSite" name="WhitelistedSite">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -212,7 +212,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.uma.model.Claim" name="Claim">
+    <entity class="cz.muni.ics.uma.model.Claim" name="Claim">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -223,7 +223,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.uma.model.Permission" name="Permission">
+    <entity class="cz.muni.ics.uma.model.Permission" name="Permission">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -234,7 +234,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.uma.model.PermissionTicket" name="PermissionTicket">
+    <entity class="cz.muni.ics.uma.model.PermissionTicket" name="PermissionTicket">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -245,7 +245,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.uma.model.Policy" name="Policy">
+    <entity class="cz.muni.ics.uma.model.Policy" name="Policy">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -256,7 +256,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.uma.model.ResourceSet" name="ResourceSet">
+    <entity class="cz.muni.ics.uma.model.ResourceSet" name="ResourceSet">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -267,7 +267,7 @@
         </attributes>
     </entity>
 
-    <entity class="org.mitre.uma.model.SavedRegisteredClient" name="SavedRegisteredClient">
+    <entity class="cz.muni.ics.uma.model.SavedRegisteredClient" name="SavedRegisteredClient">
         <attributes>
             <!-- changing generated value to sequence strategy (Oracle doesn't support identity) -->
             <id name="id">
@@ -278,4 +278,4 @@
         </attributes>
     </entity>
 
-</entity-mappings>
\ No newline at end of file
+</entity-mappings>
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/loading_temp_tables_oracle.sql b/perun-oidc-server-webapp/src/main/resources/db/oracle/loading_temp_tables_oracle.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/loading_temp_tables_oracle.sql
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/loading_temp_tables_oracle.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_index.sql b/perun-oidc-server-webapp/src/main/resources/db/oracle/oracle_database_index.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_index.sql
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/oracle_database_index.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_tables.sql b/perun-oidc-server-webapp/src/main/resources/db/oracle/oracle_database_tables.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_tables.sql
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/oracle_database_tables.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql b/perun-oidc-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/security-schema_oracle.sql b/perun-oidc-server-webapp/src/main/resources/db/oracle/security-schema_oracle.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/security-schema_oracle.sql
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/security-schema_oracle.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/users_oracle.sql b/perun-oidc-server-webapp/src/main/resources/db/oracle/users_oracle.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/oracle/users_oracle.sql
rename to perun-oidc-server-webapp/src/main/resources/db/oracle/users_oracle.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/clients.sql b/perun-oidc-server-webapp/src/main/resources/db/psql/clients.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/psql/clients.sql
rename to perun-oidc-server-webapp/src/main/resources/db/psql/clients.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_index.sql b/perun-oidc-server-webapp/src/main/resources/db/psql/psql_database_index.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/psql/psql_database_index.sql
rename to perun-oidc-server-webapp/src/main/resources/db/psql/psql_database_index.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_tables.sql b/perun-oidc-server-webapp/src/main/resources/db/psql/psql_database_tables.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/psql/psql_database_tables.sql
rename to perun-oidc-server-webapp/src/main/resources/db/psql/psql_database_tables.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/scopes.sql b/perun-oidc-server-webapp/src/main/resources/db/psql/scopes.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/psql/scopes.sql
rename to perun-oidc-server-webapp/src/main/resources/db/psql/scopes.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/security-schema.sql b/perun-oidc-server-webapp/src/main/resources/db/psql/security-schema.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/psql/security-schema.sql
rename to perun-oidc-server-webapp/src/main/resources/db/psql/security-schema.sql
diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/users.sql b/perun-oidc-server-webapp/src/main/resources/db/psql/users.sql
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/db/psql/users.sql
rename to perun-oidc-server-webapp/src/main/resources/db/psql/users.sql
diff --git a/openid-connect-server-webapp/src/main/resources/keystore.jwks b/perun-oidc-server-webapp/src/main/resources/keystore.jwks
similarity index 100%
rename from openid-connect-server-webapp/src/main/resources/keystore.jwks
rename to perun-oidc-server-webapp/src/main/resources/keystore.jwks
diff --git a/perun-oidc-server-webapp/src/main/resources/localization/cs.properties b/perun-oidc-server-webapp/src/main/resources/localization/cs.properties
new file mode 100644
index 000000000..bdd8ac27e
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/resources/localization/cs.properties
@@ -0,0 +1,173 @@
+#CONSENT
+yes=Ano, akceptuji
+no=Ne, neakceptuji
+login=Login
+consent_privacy_policy=Zásady zpracování osobních údajů pro službu
+consent_header=Obsah odesílaných osobních informací službě
+consent_title=Obsah odesílaných osobních informací službě
+remember=Příště se již neptat
+
+#APPROVE_DEVICE
+device_approve_privacy=Bezpečnostní politika služby
+device_approve_header=Schválení přístupu k Vašim datům
+device_approve_title=Schválení přístupu k Vašim datům
+
+#DEVICE_APPROVED
+device_approved_approved=Zařížení bylo autorizováno
+device_approved_rejected=Zařízení byl odmítnut přístup
+device_approved_title=Autorizace zařízení dokončena
+device_approved_text_approved_start=Zařízení bylo úspěšně autorizováno. Nyní můžete pokračovat ke službě
+device_approved_text_approved_end=na Vašem zařízení. Tahle stránka může být zavřena.
+device_approved_text_rejected_start=Zařízení byl odmítnut přístup ke službě
+device_approved_text_rejected_end=Jestli jste tak nechtěli učinit, zahajte proces autorizace od začátku. Tahle stránka může být zavřena.
+
+
+#REQUEST USER CODE
+request_code_title=Zadejte autorizační kód zařízení
+request_code_header=Zadejte autorizační kód zařízení
+user_code_empty_or_not_found=Nebyl zadán žádný kód anebo zadanej kód je nesprávný.
+user_code_expired=Platnost použitého kódu vypršela. Prosíme, vyžádejte si nový a opakujte proces.
+user_code_already_approved=Zadaný kód už byl použit. Prosíme, vyžádejte si nový a opakujte proces.
+user_code_mismatch=Zadaný kód nebyl rozpoznán. Prosíme, ověřte že jste zadali správný kód.
+user_code_error=Vyskytla se chyba pri zpracování Vašeho požadavku. Zkuste jej zopakovat.
+user_code_submit=Pokračovat
+user_code_info=Zadejte autorizační kód zobrazen na zařízení z kterého se snažíte přihlásit.
+code=Kód
+
+#IsTestSpWarning
+is_test_sp_warning_title=Varování - služba je testovací
+is_test_sp_warning_header=Varování
+is_test_sp_warning_text=Přistupujete ke službě, která je v testovacím režimu.
+is_test_sp_warning_continue=Pokračovat
+
+#CLAIMS AND SCOPES
+no_scopes=Žádné data nebudou odeslány
+openid=Identifikátor uživatele na službě
+sub=Identifikátor uživatele
+profile=Profil uživatele
+email=Email
+address=Adresa
+phone=Telefonní číslo
+offline_access=Offline přístup
+perun_api=Volání Perun API ve jménu uživatele
+groupNames=Jména skupin ve kterých je uživatel členem
+eduPersonEntitlement=Oprávnění
+permissions_ega=Povolení pro EGA datasety
+permissions_rems=Povolení pro REMS datasety
+forwardedScopedAffiliations=Vztah k domovské(ým) organizaci(cím)
+bona_fide_status=Bona fide status
+country=Krajina
+ga4gh=Global Alliance For Genomics and Health
+eppns=Identifikátory osoby v organizaci
+name=Jméno uživatele
+preferred_username=Uživatelské jméno
+given_name=Křestní jméno
+middle_name=Střední jméno
+family_name=Příjmení
+locale=Jazyk
+zoneinfo=Zóna
+phone_number=Telefon
+
+#UNAPPROVED
+contact_p=V případě nejasností nás kontaktujte na
+403_header=Přístup odmítnut
+403_text=Nemáte dostatečná práva pro přístup ke službě:
+403_informationPage=Pro více informací o službě navštivte
+403_contactSupport=Pokud si myslíte že máte mít přístup, kontaktujte administrátora:
+403_subject=Problém s přihlášením do služby
+403_isCesnetEligible_notSet_hdr=Přístup zamítnut
+403_isCesnetEligible_notSet_msg=Přístup ke službě zamítnut, protože Váš účet není z české akademické instituce. Přihlaste se, prosím, pomocí svého účtu u akademické instituce.<br/><a class="mt-2 cw btn btn-primary btn-lg btn-block" href="%%TARGET%%">Znovu přihlásit</a>
+403_isCesnetEligible_expired_hdr=Přístup zamítnut
+403_isCesnetEligible_expired_msg=Přístup ke službě zamítnut, protože plynula doba 12 měsíců od Vašeho posledního přihlášení účtem z české akademické instituce. Přihlaste se, prosím, pomocí svého účtu u akademické instituce.<br/><a class="mt-2 cw btn btn-lg btn-primary btn-block" href="%%TARGET%%">Znovu přihlásit</a>
+403_ensure_vo_hdr=Přístup zamítnut
+403_ensure_vo_msg=Nemáte dostatečná práva pro přístup ke službě
+403_authorization_hdr=Přístup zamítnut
+403_authorization_msg=Tato stránka se Vám zobrazuje, protože nemáte přístup ke službě. To může být důsledkem přístupových omezení nastavených administrátorem.
+403_not_in_test_vos_groups_hdr=Přístup zamítnut
+403_not_in_test_vos_groups_msg=Tato stránka se Vám zobrazuje, protože nemáte přístup k testovacím službám AAI.
+403_not_in_prod_vos_groups_hdr=Přístup zamítnut
+403_not_in_prod_vos_groups_msg=Tato stránka se Vám zobrazuje, protože nemáte přístup ke službám AAI.
+403_not_in_mandatory_vos_groups_hdr=Přístup zamítnut
+403_not_in_mandatory_vos_groups_msg=Tato stránka se Vám zobrazuje, protože Vaše požadované členství v organizaci je nevalidní.
+403_not_logged_in_hdr=Přístup zamítnut
+403_not_logged_in_msg=Zdá se, že přihlášení selhalo. Zkuste, prosím, zavřít Váš prohlížeč a přihlásit se znovu.
+
+#GO TO REGISTRATION
+go_to_registration_title=Je vyžadována Vaše aktivita
+go_to_registration_header1=Pro přístup ke službě
+go_to_registration_header2=je vyžadována Vaše aktivita
+go_to_registration_continue=Pokračovat na stránku s doplňujícími informacemi
+
+#REGISTRATION
+registration_title=Registrace pro přístup ke službě
+registration_header1=Přístup ke službě
+registration_header2=byl zamítnut
+registration_message=Pro získání přístupu k dané službě je nutné být členem jedné z následujících skupin. Pokračujte výběrem příslušné organizace a skupiny.
+registration_select_vo=Vyberte virtuální organizaci:
+registration_select_group=Vyberte skupinu pro registraci:
+registration_continue=Pokračovat na registrační stránku do vybrané skupiny
+
+#CESNET footer specific
+footer_other_projects=OSTATNÍ PROJEKTY
+footer_helpdesk=HELPDESK
+
+#AUP
+aup_header=Formulář s podmínkami užití
+must_agree_aup=Pro pokračování musíte souhlasit s následujícími podmínkami užití:
+org_vo=Organizace / Virtuální Organizace
+see_aup=Prohlédněte si podmínky užití ve verzi
+here=zde.
+agree_aup=Souhlasím s podmínkami užití
+
+#MUNI header specific
+unif_login="Přihlášení na MU"
+go_to_login_title=Přejít k přihlášení (Klávesová zkratka: Alt + 2)
+go_to_login_text=Přejít k přihlášení
+language=Česky
+img_name=sso
+img_width=180
+img_height=34
+other_lang=en
+other_language=English
+muni_logo=MUNI Jednotné přihlášení
+
+#MUNI footer specific
+masaryk_university=&copy; Masarykova univerzita
+service=Službu
+unified_login=Jednotné přihlášení na MU
+provided=zajišťuje
+ics=Ústav výpočetní techniky MU
+
+#Logout
+logout.confirmation.submit=Odhlásit
+logout.confirmation.deny=Zůstat přihlášen(a)
+logout.confirmation.header=Potvrzení odhlášení
+logout.confirmation.explanation=Skutečně se chcete odhlásit od poskytovatele identity?
+
+#Continue in ensure_vo
+continue_direct_title=Přesměrování na registraci
+continue_direct_header=Budete přesměrován(a)
+continue_direct_heading=Zaregistrujte se pro získaní přístupu
+continue_direct_text=Nemáte dostatečná oprávnení po přístup ke službě. Kliknutím na tlačítko níže, budete přesměrován(a) na registraci pro získání přístupu.
+continue_direct_btn=Pokračovat
+
+# SAML Logout Success (/logout_success)
+logout_success_title=Odhlášení proběhlo úspěšně
+logout_success_header=Odhlášení proběhlo úspěšně
+logout_success_msg=Byl(a) jste úspěšně odhlášen(a).
+
+# SAML Login Failure (/login_failure)
+login_failure_title=Problém s přihlášením
+login_failure_header=Problém s přihlášením
+login_failure_msg=Ups! Zdá se, že jsme Vás nemohli přihlásit. Zkuste to znovu.
+login_failure_contact_us=Pokud problém přetrvává, kontaktuje nás na
+
+# SAML Login Success (/login_success)
+login_success_title=Přihlášení proběhlo úspěšně
+login_success_header=Přihlášení proběhlo úspěšně
+login_success_msg=Byl(a) jste úspěšně přihlášen(a), avšak neregistrujeme žádnou službu, na kterou jste se pokoušel(a) přistoupit.
+
+# Logout denied (endsession endpoint with clicking NO)
+logout_denied_title=Odhlášení zrušeno
+logout_denied_header=Odhlášení zrušeno
+logout_denied_msg=Proces odhlášení byl zastaven.
diff --git a/perun-oidc-server-webapp/src/main/resources/localization/en.properties b/perun-oidc-server-webapp/src/main/resources/localization/en.properties
new file mode 100644
index 000000000..be19a55ef
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/resources/localization/en.properties
@@ -0,0 +1,172 @@
+#CONSENT
+yes=Yes, continue
+no=No, cancel
+login=login
+consent_privacy_policy=Privacy policy for the service
+consent_header=Consent about releasing personal information to service
+consent_title=Consent about releasing personal information to service
+remember=Do not ask again
+
+#APPROVE_DEVICE
+device_approve_privacy=Privacy policy for the service
+device_approve_header=Approve device to access your data
+device_approve_title=Approve device to access your data
+
+#DEVICE_APPROVED
+device_approved_approved=The device has been authorized.
+device_approved_rejected=The device has been rejected authorization.
+device_approved_title=Device approval result
+device_approved_text_approved_start=The device has been successfully authorized. You can now access the service
+device_approved_text_approved_end=on your device. You may now close this page.
+device_approved_text_rejected_start=The device has been denied access to the service
+device_approved_text_rejected_end=If you did not intend to do this, start the authorization process again. You may now close this page.
+
+#REQUEST USER CODE
+request_code_title=Enter the device authorization code
+request_code_header=Enter the device authorization code
+user_code_empty_or_not_found=No authorization code has been provided or it has not been recognized.
+user_code_expired=The authorization code you have used has expired. Please request a new one and restart the process.
+user_code_already_approved=The authorization code you have used has been already used. Please request a new one and restart the process.
+user_code_mismatch=The code you have used has not been recognized. Please verify your input.
+user_code_error=An error has occurred while processing your request. Please try it again.
+user_code_submit=Submit
+user_code_info=Enter the code displayed on the device you are trying to authenticate on.
+code=Code
+
+#IsTestSpWarning
+is_test_sp_warning_title=Warning - test service
+is_test_sp_warning_header=Warning
+is_test_sp_warning_text=You are about to access service, which is in testing environment.
+is_test_sp_warning_continue=Continue
+
+
+#CLAIMS AND SCOPES
+no_scopes=No data will be released
+sub=Identifier of user
+openid=Identifier of user on a service
+profile=Profile
+email=Email
+address=Adress
+phone=Phone number
+offline_access=Offline access
+perun_api=Calls to Perun API in the name of user
+groupNames=Names of groups that user is member of
+eduPersonEntitlement=Entitlement
+permissions_ega=Permissions for EGA datasets
+permissions_rems=Permissions for REMS datasets
+forwardedScopedAffiliations=Home organization affiliation
+bona_fide_status=Bona fide status
+country=Country
+eppns=Person principal names
+name=Name of user
+preferred_username=Username
+given_name=Given name
+middle_name=Middle name
+family_name=Family name
+locale=Language
+zoneinfo=Zone
+phone_number=Phone
+
+#UNAPPROVED
+contact_p=In case of any questions, do not hesitate to contact us at
+403_header=Access forbidden
+403_text=You don't meet the prerequisites for accessing the service:
+403_informationPage=For more information about this service please visit this
+403_contactSupport=If you think you should have an access contact service operator at
+403_subject=Problem with login to service:
+403_isCesnetEligible_notSet_hdr=Access denied
+403_isCesnetEligible_notSet_msg=Your account is not from Czech academic institution. Please log in with your account from academic institution.<a class="mt-2 cw btn btn-primary btn-lg btn-block" href="%%TARGET%%">Log in again</a>
+403_isCesnetEligible_expired_hdr=Access denied
+403_isCesnetEligible_expired_msg=Your last login, from Czech academic institution, has been registered 12 months ago. Please sign in with your account from academic institution.<a class="mt-2 cw btn btn-primary btn-lg btn-block" href="%%TARGET%%">Log in again</a>
+403_ensure_vo_hdr=Access denied
+403_ensure_vo_msg=You don't meet the prerequisites to access the service.
+403_authorization_hdr=Access denied
+403_authorization_msg=You see this page because you are not allowed to access the service. This situation can be a result of the access restrictions that the service administrator has set up.
+403_not_in_test_vos_groups_hdr=Access denied
+403_not_in_test_vos_groups_msg=You see this page because you are not allowed to access AAI's testing services.
+403_not_in_prod_vos_groups_hdr=Access denied
+403_not_in_prod_vos_groups_msg=You see this page because you are not allowed to access AAI's services.
+403_not_in_mandatory_vos_groups_hdr=Access denied
+403_not_in_mandatory_vos_groups_msg=You are seeing this page because your membership in the required organizational units is invalid.
+403_not_logged_in_hdr=Access denied
+403_not_logged_in_msg=It appears the login process has failed. Please close your browser and try to log in again.
+
+#GO TO REGISTRATION
+go_to_registration_title=Your activity is necessary
+go_to_registration_header1=Your activity is necessary to access the
+go_to_registration_header2=service
+go_to_registration_continue=Continue to a page with additional information
+
+#REGISTRATION
+registration_title=Registration for access to the service
+registration_header1=Access to the service
+registration_header2=has been forbidden
+registration_message=To access the service it is necessary to have a valid membership in one of the following groups. Please proceed with selection of organization and group for registration.
+registration_select_vo=Select virtual organization for registration:
+registration_select_group=Select group for registration:
+registration_continue=Continue to the registration page for selected group
+
+#CESNET footer specific
+footer_other_projects=OTHER CESNET PROJECTS
+footer_helpdesk=HELPDESK
+
+#AUP
+aup_header=Acceptable Usage Policy form
+must_agree_aup=You must agree to the following acceptable usage policies:
+org_vo=Organization / Virtual Organization
+see_aup=See the acceptable usage policy in version
+here=here.
+agree_aup=I agree with the acceptable usage policy
+
+#MUNI header specific
+unif_login=Unified MU login
+go_to_login_title=Go to login (Shortcut: Alt + 2)
+go_to_login_text=Go to login
+language=English
+img_name=sso-en
+img_width=160
+img_height=35
+other_lang=cs
+other_language=Česky
+muni_logo=MUNI Unified login
+
+#MUNI footer specific
+masaryk_university=&copy; Masaryk University
+service=The service
+unified_login=Unified MU login
+provided=is provided by
+ics=Institute of Computer Science
+
+#Logout
+logout.confirmation.submit=Log Out
+logout.confirmation.deny=Stay Logged In
+logout.confirmation.header=Confirm logout
+logout.confirmation.explanation=Do you want to log out of the identity provider?
+
+#Continue in ensure_vo
+continue_direct_title=Redirect to registration
+continue_direct_header=You will be redirected
+continue_direct_heading=Register to get access
+continue_direct_text=You don't meet the prerequisites to access the service. By clicking the button below, you will be redirected to a registration page, where you can apply for access.
+continue_direct_btn=Continue
+
+# SAML Logout Success (/logout_success)
+logout_success_title=Logout success
+logout_success_header=Logout success
+logout_success_msg=You have been successfully logged out.
+
+# SAML Login Failure (/login_failure)
+login_failure_title=Login failure
+login_failure_header=Login error
+login_failure_msg=Ooops! It seems like an error during the login. Please try to log in again.
+login_failure_contact_us=If the problem persists, contact us at
+
+# SAML Login Success (/login_success)
+login_success_title=Login success
+login_success_header=Login success
+login_success_msg=You have successfully logged in. However, it seems we have no service to forward you to.
+
+# Logout denied (endsession endpoint with clicking NO)
+logout_denied_title=Logout denied
+logout_denied_header=Logout canceled
+logout_denied_msg=You have canceled the logout process.
diff --git a/openid-connect-server-webapp/src/main/resources/log4j.xml b/perun-oidc-server-webapp/src/main/resources/log4j.xml
similarity index 88%
rename from openid-connect-server-webapp/src/main/resources/log4j.xml
rename to perun-oidc-server-webapp/src/main/resources/log4j.xml
index efb4074fe..3293dcfd4 100644
--- a/openid-connect-server-webapp/src/main/resources/log4j.xml
+++ b/perun-oidc-server-webapp/src/main/resources/log4j.xml
@@ -32,28 +32,28 @@
 	</appender>
 
 	<!-- Application Loggers -->
-	<logger name="org.mitre.openid">
+	<logger name="cz.muni.ics.openid">
 		<level value="info" />
 	</logger>
-	<logger name="org.mitre.oauth2">
+	<logger name="cz.muni.ics.oauth2">
 		<level value="info" />
 	</logger>
-	<logger name="org.mitre.discovery">
+	<logger name="cz.muni.ics.discovery">
 		<level value="info" />
 	</logger>
-	<logger name="org.mitre.jose">
+	<logger name="cz.muni.ics.jose">
 		<level value="info" />
 	</logger>
-	<logger name="org.mitre.jwt">
+	<logger name="cz.muni.ics.jwt">
 		<level value="info" />
 	</logger>
-	<logger name="org.mitre.util">
+	<logger name="cz.muni.ics.util">
 		<level value="info" />
 	</logger>
-	<logger name="org.mitre.uma">
+	<logger name="cz.muni.ics.uma">
 		<level value="info" />
 	</logger>
-	<logger name="org.mitre.data">
+	<logger name="cz.muni.ics.data">
 		<level value="info" />
 	</logger>
 
diff --git a/perun-oidc-server-webapp/src/main/resources/logback.xml b/perun-oidc-server-webapp/src/main/resources/logback.xml
new file mode 100644
index 000000000..30fe672a2
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/resources/logback.xml
@@ -0,0 +1,58 @@
+<configuration packagingData="true" debug="false" scan="false" scanPeriod="30 seconds">
+	<contextName>${log.contextName}</contextName>
+	<property name="PATTERN"
+			  value='%d{"yyyy-MM-dd HH:mm:ss.SSS"} %X{remoteAddr} [%X{sessionID}]: %-5level %logger{40} - %m%n%xException'/>
+	<property name="PATTERN_SYSLOG"
+			  value='%X{remoteAddr} [%X{sessionID}] %cn: %-5level %logger{40} - %m%n%xException'/>
+
+	<!-- ROLLING FILE -->
+	<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.rolling-file}.${log.file-extension}</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.rolling-file}.${log.file-extension}.%d{yyyy-MM-dd}</fileNamePattern>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- FILE -->
+	<appender name="FILE" class="ch.qos.logback.core.FileAppender">
+		<file>${log.file}.${log.file-extension}</file>
+		<encoder>
+			<pattern>${PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- SYSLOG -->
+	<appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender">
+		<!-- PASSED FROM POM.XML / MAVEN BUILD PROPS -->
+		<facility>${log.facility}</facility>
+		<throwableExcluded>true</throwableExcluded>
+		<suffixPattern>${PATTERN_SYSLOG}</suffixPattern>
+	</appender>
+
+	<root level="${log.level}">
+		<appender-ref ref="${log.to}"/>
+	</root>
+
+	<!-- keep Spring quiet -->
+	<logger name="org.springframework" level="warn"/>
+	<logger name="org.springframework.security.core.SpringSecurityCoreVersion" level="info"/>
+	<logger name="cz.muni.ics.openid.connect.config.JsonMessageSource" level="warn"/>
+	<logger name="org.apache" level="warn"/>
+	<logger name="org.apache.directory" level="warn"/>
+	<logger name="org.apache.directory.ldap.client.api.LdapNetworkConnection" level="error"/>
+	<logger name="com.zaxxer.hikari" level="warn"/>
+	<logger name="cz.muni.ics" level="info"/>
+	<logger name="org.opensaml" level="info"/>
+	<logger name="org.springframework.security.saml" level="debug"/>
+	<logger name="PROTOCOL_MESSAGE" level="warn"/>
+	<!-- PASSED FROM POM.XML / MAVEN BUILD PROPS -->
+	<logger name="cz.muni.ics.oidc" level="${log.level}"/>
+	<logger name="cz.muni.ics.oidc.aop.WebLoggingAspect" level="debug"/>
+	<logger name="cz.muni.ics.oidc.aop.ExecutionTimeLoggingAspect" level="trace"/>
+	<logger name="cz.muni.ics.openid.connect.web.EndSessionEndpoint" level="${log.level}"/>
+	<logger name="net.javacrumbs.shedlock" level="error"/>
+
+</configuration>
diff --git a/perun-oidc-server-webapp/src/main/resources/web_classes/web_html_classes.properties b/perun-oidc-server-webapp/src/main/resources/web_classes/web_html_classes.properties
new file mode 100644
index 000000000..d5fa0d22d
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/resources/web_classes/web_html_classes.properties
@@ -0,0 +1,4 @@
+perun-attrname.h2.class=h4 oh mb-0 mt-0
+perun-attrname.label.class=h4 mb-0 mt-0
+perun-attrcontainer.ul.class=
+perun-attrlist.h3.class=h5 mb-0 mt-0
\ No newline at end of file
diff --git a/openid-connect-server-webapp/src/main/webapp/META-INF/MANIFEST.MF b/perun-oidc-server-webapp/src/main/webapp/META-INF/MANIFEST.MF
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/META-INF/MANIFEST.MF
rename to perun-oidc-server-webapp/src/main/webapp/META-INF/MANIFEST.MF
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/application-context.xml
similarity index 76%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/application-context.xml
index ed566e002..dae4f464f 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/application-context.xml
@@ -33,7 +33,7 @@
 		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
 
 	<!-- Scan for components -->
-	<context:component-scan annotation-config="true" base-package="org.mitre" />
+	<context:component-scan annotation-config="true" base-package="cz.muni.ics" />
 
 	<!-- Enables the Spring MVC @Controller programming model -->
 	<tx:annotation-driven transaction-manager="transactionManager" />
@@ -44,23 +44,23 @@
 		</mvc:message-converters>
 	</mvc:annotation-driven>
 
-	<bean id="userInfoInterceptor" class="org.mitre.openid.connect.web.UserInfoInterceptor" />
-	<bean id="serverConfigInterceptor" class="org.mitre.openid.connect.web.ServerConfigInterceptor" />
+	<bean id="userInfoInterceptor" class="cz.muni.ics.openid.connect.web.UserInfoInterceptor" />
+	<bean id="serverConfigInterceptor" class="cz.muni.ics.openid.connect.web.ServerConfigInterceptor" />
 	<mvc:interceptors>
 		<mvc:interceptor>
 			<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
 			<mvc:mapping path="/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
 			<mvc:exclude-mapping path="/resources/**" />
 			<mvc:exclude-mapping path="/token**"/>
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.UserInfoEndpoint).URL}**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.RootController).API_URL}/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.oauth2.web.DeviceEndpoint).URL}/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.oauth2.web.IntrospectionEndpoint).URL}**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.oauth2.web.RevocationEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
 					 
 			<!-- Inject the UserInfo into the response -->
 			<ref bean="userInfoInterceptor"/>
@@ -68,17 +68,17 @@
 		<mvc:interceptor>
 			<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
 			<mvc:mapping path="/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
 			<mvc:exclude-mapping path="/resources/**" />
 			<mvc:exclude-mapping path="/token**"/>
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.UserInfoEndpoint).URL}**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.openid.connect.web.RootController).API_URL}/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.oauth2.web.DeviceEndpoint).URL}/**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.oauth2.web.IntrospectionEndpoint).URL}**" />
-			<mvc:exclude-mapping path="/#{T(org.mitre.oauth2.web.RevocationEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
 			<!-- Inject the server configuration into the response -->
 			<ref bean="serverConfigInterceptor"/>
 		</mvc:interceptor>
@@ -102,7 +102,7 @@
 
 	<oauth:web-expression-handler id="oauthWebExpressionHandler" />
 
-	<bean id="mdcFilter" class="org.mitre.mdc.MultiMDCFilter"/>
+	<bean id="mdcFilter" class="cz.muni.ics.mdc.MultiMDCFilter"/>
 
 	<!-- Spring Security configuration -->
 
@@ -127,14 +127,14 @@
 	</security:http>
 
 	<!-- Allow open access to discovery endpoints -->
-	<security:http pattern="/#{T(org.mitre.openid.connect.web.JWKSetPublishingEndpoint).URL}**" use-expressions="true" entry-point-ref="http403EntryPoint" create-session="stateless">
-		<security:intercept-url pattern="/#{T(org.mitre.openid.connect.web.JWKSetPublishingEndpoint).URL}**" access="permitAll"/>
+	<security:http pattern="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" use-expressions="true" entry-point-ref="http403EntryPoint" create-session="stateless">
+		<security:intercept-url pattern="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" access="permitAll"/>
 		<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
 		<security:custom-filter ref="mdcFilter" before="FIRST"/>
 		<security:csrf disabled="true"/>
 	</security:http>
-	<security:http pattern="/#{T(org.mitre.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" use-expressions="true" entry-point-ref="http403EntryPoint" create-session="stateless">
-		<security:intercept-url pattern="/#{T(org.mitre.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" access="permitAll"/>
+	<security:http pattern="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" use-expressions="true" entry-point-ref="http403EntryPoint" create-session="stateless">
+		<security:intercept-url pattern="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" access="permitAll"/>
 		<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
 		<security:custom-filter ref="mdcFilter" before="FIRST"/>
 		<security:csrf disabled="true"/>
@@ -149,7 +149,7 @@
 	</security:http>
 	
 	<!-- OAuth-protect API and other endpoints -->
-	<security:http pattern="/#{T(org.mitre.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint" create-session="stateless">
+	<security:http pattern="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint" create-session="stateless">
 		<security:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
 		<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
 		<security:custom-filter ref="mdcFilter" before="FIRST"/>
@@ -158,7 +158,7 @@
 		<security:csrf disabled="true"/>
 	</security:http>
 
-	<security:http pattern="/#{T(org.mitre.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint" create-session="stateless">
+	<security:http pattern="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint" create-session="stateless">
 		<security:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
 		<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
 		<security:custom-filter ref="mdcFilter" before="FIRST"/>
@@ -167,7 +167,7 @@
 		<security:csrf disabled="true"/>
 	</security:http>
 
-	<security:http pattern="/#{T(org.mitre.openid.connect.web.UserInfoEndpoint).URL}**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint" create-session="stateless">
+	<security:http pattern="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint" create-session="stateless">
 		<security:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
 		<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
 		<security:custom-filter ref="mdcFilter" before="FIRST"/>
@@ -175,14 +175,14 @@
 		<security:csrf disabled="true"/>
 	</security:http>
 
- 	<security:http pattern="/#{T(org.mitre.openid.connect.web.RootController).API_URL}/**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint" create-session="never">
+ 	<security:http pattern="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" use-expressions="true" entry-point-ref="oauthAuthenticationEntryPoint" create-session="never">
 		<security:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
 		<security:custom-filter ref="mdcFilter" before="FIRST"/>
 		<security:expression-handler ref="oauthWebExpressionHandler" />
 		<security:csrf disabled="true"/>
 	</security:http>
 	
- 	<security:http pattern="/#{T(org.mitre.oauth2.web.DeviceEndpoint).URL}/**" 
+ 	<security:http pattern="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**"
  		use-expressions="true" 
  		entry-point-ref="oauthAuthenticationEntryPoint" 
  		create-session="stateless"
@@ -197,7 +197,7 @@
 		<security:csrf disabled="true"/>
 	</security:http>
 	
-	<security:http pattern="/#{T(org.mitre.oauth2.web.IntrospectionEndpoint).URL}**" 
+	<security:http pattern="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**"
 			use-expressions="true" 
 			entry-point-ref="oauthAuthenticationEntryPoint" 
 			create-session="stateless"
@@ -211,7 +211,7 @@
 		<security:csrf disabled="true"/>
 	</security:http>
 
-	<security:http pattern="/#{T(org.mitre.oauth2.web.RevocationEndpoint).URL}**"
+	<security:http pattern="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**"
 			use-expressions="true" 
 			entry-point-ref="oauthAuthenticationEntryPoint" 
 			create-session="stateless"
@@ -241,7 +241,7 @@
 
 	<bean id="oauth2ExceptionTranslator" class="org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator" />
 
-	<bean id="clientAuthMatcher" class="org.mitre.openid.connect.filter.MultiUrlRequestMatcher">
+	<bean id="clientAuthMatcher" class="cz.muni.ics.openid.connect.filter.MultiUrlRequestMatcher">
 		<constructor-arg name="filterProcessesUrls">
 			<set>
 				<value>/introspect</value>
@@ -256,7 +256,7 @@
 		<property name="requiresAuthenticationRequestMatcher" ref="clientAuthMatcher" />
 	</bean>
 	
-	<bean id="clientAssertionEndpointFilter" class="org.mitre.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter">
+	<bean id="clientAssertionEndpointFilter" class="cz.muni.ics.openid.connect.assertion.JWTBearerClientAssertionTokenEndpointFilter">
 		<constructor-arg name="additionalMatcher" ref="clientAuthMatcher" />
 		<property name="authenticationManager" ref="clientAssertionAuthenticationManager" />
 	</bean>
@@ -270,7 +270,7 @@
 		<security:authentication-provider ref="clientAssertionAuthenticationProvider" />
 	</security:authentication-manager>
 	
-	<bean id="clientAssertionAuthenticationProvider" class="org.mitre.openid.connect.assertion.JWTBearerAuthenticationProvider" />
+	<bean id="clientAssertionAuthenticationProvider" class="cz.muni.ics.openid.connect.assertion.JWTBearerAuthenticationProvider" />
 
 	<!-- Configure locale information -->
 	<import resource="locale-config.xml" />
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml
similarity index 83%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml
index 0ec4ce7f6..4836d3614 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml
@@ -29,16 +29,16 @@
 		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
 
 	<!-- validate incoming tokens for JWT assertions -->
-	<bean id="jwtAssertionValidator" class="org.mitre.jwt.assertion.impl.NullAssertionValidator" />
+	<bean id="jwtAssertionValidator" class="cz.muni.ics.jwt.assertion.impl.NullAssertionValidator" />
 
 	<!-- translate incoming assertions to token authorization objects -->
-	<bean id="jwtAssertionTokenFactory" class="org.mitre.oauth2.assertion.impl.DirectCopyRequestFactory" />
+	<bean id="jwtAssertionTokenFactory" class="cz.muni.ics.oauth2.assertion.impl.DirectCopyRequestFactory" />
 
 	<!-- validate client software statements for dynamic registration -->
-<!-- 	<bean id="clientAssertionValidator" class="org.mitre.jwt.assertion.impl.NullAssertionValidator" /> -->
+<!-- 	<bean id="clientAssertionValidator" class="cz.muni.ics.jwt.assertion.impl.NullAssertionValidator" /> -->
 	
 	<!-- this class will pass assertions signed by the issuers and keys in the whitelist -->
-	<bean id="clientAssertionValidator" class="org.mitre.jwt.assertion.impl.WhitelistedIssuerAssertionValidator">
+	<bean id="clientAssertionValidator" class="cz.muni.ics.jwt.assertion.impl.WhitelistedIssuerAssertionValidator">
 		<property name="whitelist">
 			<map>
 				<entry key="http://artemesia.local" value="http://localhost:8080/openid-connect-server-webapp/jwk" />
@@ -47,4 +47,4 @@
 	</bean>
 
 
-</beans>
\ No newline at end of file
+</beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/authz-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/authz-config.xml
similarity index 96%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/authz-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/authz-config.xml
index 4d5242ae1..4ca0109b9 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/authz-config.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/authz-config.xml
@@ -52,7 +52,7 @@
 
 	<bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
 
-	<bean id="oauthRequestValidator" class="org.mitre.oauth2.token.ScopeServiceAwareOAuth2RequestValidator" />
+	<bean id="oauthRequestValidator" class="cz.muni.ics.oauth2.token.ScopeServiceAwareOAuth2RequestValidator" />
 
 	<!-- Error page handler. -->
 	<mvc:view-controller path="/error" view-name="error" />
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/acrs.sql b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/acrs.sql
new file mode 100644
index 000000000..0013f501a
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/acrs.sql
@@ -0,0 +1,22 @@
+CREATE TABLE IF NOT EXISTS acrs (
+    id BIGINT AUTO_INCREMENT,
+    client_id VARCHAR(2048) NOT NULL,
+    sub VARCHAR(2048) NOT NULL,
+    state VARCHAR(2048) NOT NULL,
+    shib_authn_context_class VARCHAR(2048) NOT NULL,
+    expiration BIGINT NOT NULL,
+    PRIMARY KEY (id)
+);
+
+ALTER TABLE  acrs MODIFY COLUMN expiration BIGINT;
+
+CREATE TABLE IF NOT EXISTS device_code_acrs (
+    id BIGINT AUTO_INCREMENT,
+    device_code VARCHAR(2048) NOT NULL,
+    user_code VARCHAR(2048) NOT NULL,
+    shib_authn_context_class VARCHAR(2048),
+    expiration BIGINT NOT NULL,
+    PRIMARY KEY (id)
+);
+
+ALTER TABLE device_code_acrs MODIFY COLUMN expiration BIGINT;
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/db_update.sql b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/db_update.sql
new file mode 100644
index 000000000..733cc6e7d
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/db_update.sql
@@ -0,0 +1,10 @@
+ALTER TABLE authentication_holder_request_parameter
+MODIFY COLUMN val TEXT;
+
+CREATE TABLE shedlock(
+    name VARCHAR(64),
+    lock_until TIMESTAMP(3) NULL,
+    locked_at TIMESTAMP(3) NULL,
+    locked_by  VARCHAR(255),
+    PRIMARY KEY (name)
+);
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/scopes.sql b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/scopes.sql
new file mode 100644
index 000000000..15bb8f1ef
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/mysql/scopes.sql
@@ -0,0 +1,39 @@
+--
+-- Turn off autocommit and start a transaction so that we can use the temp tables
+--
+
+SET AUTOCOMMIT = 0;
+
+START TRANSACTION;
+
+CREATE TEMPORARY TABLE IF NOT EXISTS system_scope_TEMP (
+  scope VARCHAR(256),
+  description VARCHAR(4096),
+  icon VARCHAR(256),
+  restricted BOOLEAN,
+  default_scope BOOLEAN
+);
+--
+-- Insert scope information into the temporary tables.
+-- 
+
+INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES
+  ('openid', 'log in using your identity', 'user', false, true),
+  ('profile', 'basic profile information', 'list-alt', false, true),
+  ('email', 'email address', 'envelope', false, true),
+  ('address', 'physical address', 'home', false, true),
+  ('phone', 'telephone number', 'bell', false, true),
+  ('offline_access', 'offline access', 'time', false, false),
+  ('perun_api', 'calls to Perun API in your roles', 'cog', true, false);
+  
+--
+-- Merge the temporary scopes safely into the database. This is a two-step process to keep scopes from being created on every startup with a persistent store.
+--
+
+INSERT INTO system_scope (scope, description, icon, restricted, default_scope)
+  SELECT scope, description, icon, restricted, default_scope FROM system_scope_TEMP
+  ON DUPLICATE KEY UPDATE system_scope.scope = system_scope.scope;
+
+COMMIT;
+
+SET AUTOCOMMIT = 1;
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/acrs.sql b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/acrs.sql
new file mode 100644
index 000000000..96f415140
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/acrs.sql
@@ -0,0 +1,12 @@
+CREATE TABLE IF NOT EXISTS acrs (
+    id BIGINT AUTO_INCREMENT,
+    client_id VARCHAR(2048) NOT NULL,
+    sub VARCHAR(2048) NOT NULL,
+    acr_values VARCHAR(2048) NOT NULL,
+    state VARCHAR(2048) NOT NULL,
+    shib_authn_context_class VARCHAR(2048) NOT NULL,
+    expiration BIGINT NOT NULL,
+    PRIMARY KEY (id)
+);
+
+ALTER TABLE  acrs MODIFY COLUMN expiration BIGINT;
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/db_update.sql b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/db_update.sql
new file mode 100644
index 000000000..5eca832d6
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/db_update.sql
@@ -0,0 +1,7 @@
+CREATE TABLE shedlock(
+    name VARCHAR(64),
+    lock_until TIMESTAMP(3) NULL,
+    locked_at TIMESTAMP(3) NULL,
+    locked_by  VARCHAR(255),
+    PRIMARY KEY (name)
+);
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/loading_temp_tables.sql b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/loading_temp_tables.sql
new file mode 100644
index 000000000..37b0092e7
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/loading_temp_tables.sql
@@ -0,0 +1,73 @@
+--
+-- Temporary tables used during the bootstrapping process to safely load users and clients.
+-- These are not needed if you're not using the users.sql/clients.sql files to bootstrap the database.
+--
+
+CREATE TEMPORARY TABLE IF NOT EXISTS authorities_TEMP (
+      username varchar(50) not null,
+      authority varchar(50) not null,
+      constraint ix_authority_TEMP unique (username,authority));
+      
+CREATE TEMPORARY TABLE IF NOT EXISTS users_TEMP (
+      username varchar(50) not null primary key,
+      password varchar(50) not null,
+      enabled boolean not null);
+
+CREATE TEMPORARY TABLE IF NOT EXISTS user_info_TEMP (
+	sub VARCHAR(256) not null primary key,
+	preferred_username VARCHAR(256),
+	name VARCHAR(256),
+	given_name VARCHAR(256),
+	family_name VARCHAR(256),
+	middle_name VARCHAR(256),
+	nickname VARCHAR(256),
+	profile VARCHAR(256),
+	picture VARCHAR(256),
+	website VARCHAR(256),
+	email VARCHAR(256),
+	email_verified BOOLEAN,
+	gender VARCHAR(256),
+	zone_info VARCHAR(256),
+	locale VARCHAR(256),
+	phone_number VARCHAR(256),
+	address_id VARCHAR(256),
+	updated_time VARCHAR(256),
+	birthdate VARCHAR(256)
+);
+
+CREATE TEMPORARY TABLE IF NOT EXISTS client_details_TEMP (
+	client_description VARCHAR(256),
+	dynamically_registered BOOLEAN,
+	id_token_validity_seconds BIGINT,
+	
+	client_id VARCHAR(256),
+	client_secret VARCHAR(2048),
+	access_token_validity_seconds BIGINT,
+	refresh_token_validity_seconds BIGINT,
+	allow_introspection BOOLEAN,
+	
+	client_name VARCHAR(256)
+);
+
+CREATE TEMPORARY TABLE IF NOT EXISTS client_scope_TEMP (
+	owner_id VARCHAR(256),
+	scope VARCHAR(2048)
+);
+
+CREATE TEMPORARY TABLE IF NOT EXISTS client_redirect_uri_TEMP (
+	owner_id VARCHAR(256),
+	redirect_uri VARCHAR(2048) 
+);
+
+CREATE TEMPORARY TABLE IF NOT EXISTS client_grant_type_TEMP (
+	owner_id VARCHAR(256),
+	grant_type VARCHAR(2000)
+);
+
+CREATE TEMPORARY TABLE IF NOT EXISTS system_scope_TEMP (
+	scope VARCHAR(256),
+	description VARCHAR(4096),
+	icon VARCHAR(256),
+	restricted BOOLEAN,
+	default_scope BOOLEAN
+);
\ No newline at end of file
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/psql_database_tables.sql b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/psql_database_tables.sql
new file mode 100644
index 000000000..9a5c867d5
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/psql_database_tables.sql
@@ -0,0 +1,384 @@
+--
+-- Tables for OIDC Server functionality, PostgreSQL
+--
+
+CREATE TABLE IF NOT EXISTS access_token (
+	id SERIAL PRIMARY KEY,
+	token_value VARCHAR(4096),
+	expiration TIMESTAMP,
+	token_type VARCHAR(256),
+	refresh_token_id BIGINT,
+	client_id BIGINT,
+	auth_holder_id BIGINT,
+	approved_site_id BIGINT,
+	UNIQUE(token_value)
+);
+
+CREATE TABLE IF NOT EXISTS access_token_permissions (
+	access_token_id BIGINT NOT NULL,
+	permission_id BIGINT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS address (
+	id SERIAL PRIMARY KEY,
+	formatted VARCHAR(256),
+	street_address VARCHAR(256),
+	locality VARCHAR(256),
+	region VARCHAR(256),
+	postal_code VARCHAR(256),
+	country VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS approved_site (
+	id SERIAL PRIMARY KEY,
+	user_id VARCHAR(256),
+	client_id VARCHAR(256),
+	creation_date TIMESTAMP,
+	access_date TIMESTAMP,
+	timeout_date TIMESTAMP,
+	whitelisted_site_id BIGINT
+);
+
+CREATE TABLE IF NOT EXISTS approved_site_scope (
+	owner_id BIGINT,
+	scope VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS authentication_holder (
+	id SERIAL PRIMARY KEY,
+	user_auth_id BIGINT,
+	approved BOOLEAN,
+	redirect_uri VARCHAR(2048),
+	client_id VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS authentication_holder_authority (
+	owner_id BIGINT,
+	authority VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS authentication_holder_resource_id (
+	owner_id BIGINT,
+	resource_id VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS authentication_holder_response_type (
+	owner_id BIGINT,
+	response_type VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS authentication_holder_extension (
+	owner_id BIGINT,
+	extension VARCHAR(2048),
+	val VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS authentication_holder_scope (
+	owner_id BIGINT,
+	scope VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS authentication_holder_request_parameter (
+	owner_id BIGINT,
+	param VARCHAR(2048),
+	val TEXT
+);
+
+CREATE TABLE IF NOT EXISTS saved_user_auth (
+	id SERIAL PRIMARY KEY,
+	name VARCHAR(1024),
+	authenticated BOOLEAN,
+	source_class VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS saved_user_auth_authority (
+	owner_id BIGINT,
+	authority VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS client_authority (
+	owner_id BIGINT,
+	authority VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS authorization_code (
+	id SERIAL PRIMARY KEY,
+	code VARCHAR(256),
+	auth_holder_id BIGINT,
+	expiration TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS client_grant_type (
+	owner_id BIGINT,
+	grant_type VARCHAR(2000)
+);
+
+CREATE TABLE IF NOT EXISTS client_response_type (
+	owner_id BIGINT,
+	response_type VARCHAR(2000)
+);
+
+CREATE TABLE IF NOT EXISTS blacklisted_site (
+	id SERIAL PRIMARY KEY,
+	uri VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS client_details (
+	id SERIAL PRIMARY KEY,
+
+	client_description VARCHAR(1024),
+	reuse_refresh_tokens BOOLEAN DEFAULT true NOT NULL,
+	dynamically_registered BOOLEAN DEFAULT false NOT NULL,
+	allow_introspection BOOLEAN DEFAULT false NOT NULL,
+	id_token_validity_seconds BIGINT DEFAULT 600 NOT NULL,
+	device_code_validity_seconds BIGINT,
+	
+	client_id VARCHAR(256),
+	client_secret VARCHAR(2048),
+	access_token_validity_seconds BIGINT,
+	refresh_token_validity_seconds BIGINT,
+	
+	application_type VARCHAR(256),
+	client_name VARCHAR(256),
+	token_endpoint_auth_method VARCHAR(256),
+	subject_type VARCHAR(256),
+	
+	logo_uri VARCHAR(2048),
+	policy_uri VARCHAR(2048),
+	client_uri VARCHAR(2048),
+	tos_uri VARCHAR(2048),
+
+	jwks_uri VARCHAR(2048),
+	jwks VARCHAR(8192),
+	sector_identifier_uri VARCHAR(2048),
+	
+	request_object_signing_alg VARCHAR(256),
+	
+	user_info_signed_response_alg VARCHAR(256),
+	user_info_encrypted_response_alg VARCHAR(256),
+	user_info_encrypted_response_enc VARCHAR(256),
+	
+	id_token_signed_response_alg VARCHAR(256),
+	id_token_encrypted_response_alg VARCHAR(256),
+	id_token_encrypted_response_enc VARCHAR(256),
+	
+	token_endpoint_auth_signing_alg VARCHAR(256),
+	
+	default_max_age BIGINT,
+	require_auth_time BOOLEAN,
+	created_at TIMESTAMP,
+	initiate_login_uri VARCHAR(2048),
+	clear_access_tokens_on_refresh BOOLEAN DEFAULT true NOT NULL,
+	
+	software_statement VARCHAR(4096),
+	software_id VARCHAR(2048),
+	software_version VARCHAR(2048),
+	
+	code_challenge_method VARCHAR(256),
+	
+	UNIQUE (client_id)
+);
+
+CREATE TABLE IF NOT EXISTS client_request_uri (
+	owner_id BIGINT,
+	request_uri VARCHAR(2000)
+);
+
+CREATE TABLE IF NOT EXISTS client_post_logout_redirect_uri (
+	owner_id BIGINT,
+	post_logout_redirect_uri VARCHAR(2000)
+);
+
+CREATE TABLE IF NOT EXISTS client_default_acr_value (
+	owner_id BIGINT,
+	default_acr_value VARCHAR(2000)
+);
+
+CREATE TABLE IF NOT EXISTS client_contact (
+	owner_id BIGINT,
+	contact VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS client_redirect_uri (
+	owner_id BIGINT, 
+	redirect_uri VARCHAR(2048) 
+);
+
+CREATE TABLE IF NOT EXISTS client_claims_redirect_uri (
+	owner_id BIGINT, 
+	redirect_uri VARCHAR(2048) 
+);
+
+CREATE TABLE IF NOT EXISTS refresh_token (
+	id SERIAL PRIMARY KEY,
+	token_value VARCHAR(4096),
+	expiration TIMESTAMP,
+	auth_holder_id BIGINT,
+	client_id BIGINT
+);
+
+CREATE TABLE IF NOT EXISTS client_resource (
+	owner_id BIGINT, 
+	resource_id VARCHAR(256) 
+);
+
+CREATE TABLE IF NOT EXISTS client_scope (
+	owner_id BIGINT,
+	scope VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS token_scope (
+	owner_id BIGINT,
+	scope VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS system_scope (
+	id SERIAL PRIMARY KEY,
+	scope VARCHAR(256) NOT NULL,
+	description VARCHAR(4096),
+	icon VARCHAR(256),
+	restricted BOOLEAN DEFAULT false NOT NULL,
+	default_scope BOOLEAN DEFAULT false NOT NULL,
+	UNIQUE (scope)
+);
+
+CREATE TABLE IF NOT EXISTS user_info (
+	id SERIAL PRIMARY KEY,
+	sub VARCHAR(256),
+	preferred_username VARCHAR(256),
+	name VARCHAR(256),
+	given_name VARCHAR(256),
+	family_name VARCHAR(256),
+	middle_name VARCHAR(256),
+	nickname VARCHAR(256),
+	profile VARCHAR(256),
+	picture VARCHAR(256),
+	website VARCHAR(256),
+	email VARCHAR(256),
+	email_verified BOOLEAN,
+	gender VARCHAR(256),
+	zone_info VARCHAR(256),
+	locale VARCHAR(256),
+	phone_number VARCHAR(256),
+	phone_number_verified BOOLEAN,
+	address_id VARCHAR(256),
+	updated_time VARCHAR(256),
+	birthdate VARCHAR(256),
+	src VARCHAR(4096)
+);
+
+CREATE TABLE IF NOT EXISTS whitelisted_site (
+	id SERIAL PRIMARY KEY,
+	creator_user_id VARCHAR(256),
+	client_id VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS whitelisted_site_scope (
+	owner_id BIGINT,
+	scope VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS pairwise_identifier (
+	id SERIAL PRIMARY KEY,
+	identifier VARCHAR(256),
+	sub VARCHAR(256),
+	sector_identifier VARCHAR(2048)
+);
+
+CREATE TABLE IF NOT EXISTS resource_set (
+	id SERIAL PRIMARY KEY,
+	name VARCHAR(1024) NOT NULL,
+	uri VARCHAR(1024),
+	icon_uri VARCHAR(1024),
+	rs_type VARCHAR(256),
+	owner VARCHAR(256) NOT NULL,
+	client_id VARCHAR(256)
+);
+
+CREATE TABLE IF NOT EXISTS resource_set_scope (
+	owner_id BIGINT NOT NULL,
+	scope VARCHAR(256) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS permission_ticket (
+	id SERIAL PRIMARY KEY,
+	ticket VARCHAR(256) NOT NULL,
+	permission_id BIGINT NOT NULL,
+	expiration TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS permission (
+	id SERIAL PRIMARY KEY,
+	resource_set_id BIGINT
+);
+
+CREATE TABLE IF NOT EXISTS permission_scope (
+	owner_id BIGINT NOT NULL,
+	scope VARCHAR(256) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS claim (
+	id SERIAL PRIMARY KEY,
+	name VARCHAR(256),
+	friendly_name VARCHAR(1024),
+	claim_type VARCHAR(1024),
+	claim_value VARCHAR(1024)
+);
+
+CREATE TABLE IF NOT EXISTS claim_to_policy (
+	policy_id BIGINT NOT NULL,
+	claim_id BIGINT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS claim_to_permission_ticket (
+	permission_ticket_id BIGINT NOT NULL,
+	claim_id BIGINT NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS policy (
+	id SERIAL PRIMARY KEY,
+	name VARCHAR(1024),
+	resource_set_id BIGINT
+);
+
+CREATE TABLE IF NOT EXISTS policy_scope (
+	owner_id BIGINT NOT NULL,
+	scope VARCHAR(256) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS claim_token_format (
+	owner_id BIGINT NOT NULL,
+	claim_token_format VARCHAR(1024)
+);
+
+CREATE TABLE IF NOT EXISTS claim_issuer (
+	owner_id BIGINT NOT NULL,
+	issuer VARCHAR(1024)
+);
+
+CREATE TABLE IF NOT EXISTS saved_registered_client (
+	id SERIAL PRIMARY KEY,
+	issuer VARCHAR(1024),
+	registered_client VARCHAR(8192)
+);
+
+CREATE TABLE IF NOT EXISTS device_code (
+	id BIGSERIAL PRIMARY KEY,
+	device_code VARCHAR(1024),
+	user_code VARCHAR(1024),
+	expiration TIMESTAMP NULL,
+	client_id VARCHAR(256),
+	approved BOOLEAN,
+	auth_holder_id BIGINT	
+);
+
+CREATE TABLE IF NOT EXISTS device_code_scope (
+	owner_id BIGINT NOT NULL,
+	scope VARCHAR(256) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS device_code_request_parameter (
+	owner_id BIGINT,
+	param VARCHAR(2048),
+	val VARCHAR(2048)
+);
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/scopes.sql b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/scopes.sql
new file mode 100644
index 000000000..e316b04db
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/classes/db/psql/scopes.sql
@@ -0,0 +1,35 @@
+--
+-- Turn off autocommit and start a transaction so that we can use the temp tables
+--
+
+--SET AUTOCOMMIT = OFF;
+
+START TRANSACTION;
+
+--
+-- Insert scope information into the temporary tables.
+-- 
+
+INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES
+  ('openid', 'log in using your identity', 'user', false, true),
+  ('profile', 'basic profile information', 'list-alt', false, true),
+  ('email', 'email address', 'envelope', false, true),
+  ('address', 'physical address', 'home', false, true),
+  ('phone', 'telephone number', 'bell', false, true),
+  ('offline_access', 'offline access', 'time', false, false),
+  ('perun_api', 'calls to Perun API in your roles', 'cog', true, false)
+  ;
+  
+--
+-- Merge the temporary scopes safely into the database. This is a two-step process to keep scopes from being created on every startup with a persistent store.
+--
+
+INSERT INTO system_scope (scope, description, icon, restricted, default_scope)
+  SELECT scope, description, icon, restricted, default_scope FROM system_scope_TEMP
+  ON CONFLICT(scope)
+  DO NOTHING;
+
+COMMIT;
+
+--SET AUTOCOMMIT = ON;
+
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml
similarity index 88%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml
index c1e47a8dc..933b5c333 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml
@@ -30,17 +30,17 @@
 		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
 		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
 
-	<bean id="defaultKeyStore" class="org.mitre.jose.keystore.JWKSetKeyStore">
+	<bean id="defaultKeyStore" class="cz.muni.ics.jose.keystore.JWKSetKeyStore">
 		<property name="location" value="classpath:keystore.jwks" />
 	</bean>
 	
-	<bean id="defaultsignerService" class="org.mitre.jwt.signer.service.impl.DefaultJWTSigningAndValidationService">
+	<bean id="defaultsignerService" class="cz.muni.ics.jwt.signer.service.impl.DefaultJWTSigningAndValidationService">
 		<constructor-arg name="keyStore" ref="defaultKeyStore" />
 		<property name="defaultSignerKeyId" value="rsa1" />
  		<property name="defaultSigningAlgorithmName" value="RS256" />
 	</bean>
 
-	<bean id="defaultEncryptionService" class="org.mitre.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService">
+	<bean id="defaultEncryptionService" class="cz.muni.ics.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService">
 		<constructor-arg name="keyStore" ref="defaultKeyStore" />
 		<property name="defaultAlgorithm" value="RSA1_5" />
 		<property name="defaultDecryptionKeyId" value="rsa1" />
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/data-context.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/data-context.xml
new file mode 100644
index 000000000..b484eb6c9
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/data-context.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Copyright 2018 The MIT Internet Trust Consortium
+   
+    Portions copyright 2011-2013 The MITRE Corporation
+   
+    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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	   xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
+       						http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd">
+
+	<!--  The following is for connecting to a PostgreSQL database that has been initialized with
+			src/main/resources/db/psql/psql_database_tables.sql -->
+	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
+		<property name="driverClassName" value="${jdbc.driver}" />
+		<property name="jdbcUrl" value="${jdbc.url}" />
+		<property name="username" value="${jdbc.user}" />
+		<property name="password" value="${jdbc.password}" />
+		<property name="maximumPoolSize" value="50" />
+	</bean>
+
+	<bean id="mitreIdStats" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
+		<property name="driverClassName" value="${jdbc.driver}" />
+		<property name="jdbcUrl" value="${stats.jdbc.url}" />
+		<property name="username" value="${stats.jdbc.user}" />
+		<property name="password" value="${stats.jdbc.password}" />
+	</bean>
+
+	<bean id="jpaAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
+		<property name="databasePlatform" value="${jdbc.platform}" />
+		<property name="showSql" value="false" />
+	</bean>
+	<!-- Initialize the database
+ 	<jdbc:initialize-database data-source="dataSource">
+ 		<jdbc:script location="classpath:/db/mysql/mysql_database_tables.sql"/>
+ 		<jdbc:script location="classpath:/db/mysql/scopes.sql"/>
+ 	</jdbc:initialize-database>
+ 	-->
+
+</beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml
similarity index 69%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml
index 14fbcf2ea..44390d5de 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml
@@ -29,6 +29,18 @@
 		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
 
 
-<!-- This file allows you to define additional endpoints, it's normally empty in the OIDC server and has entries in the UMA server -->
+	<security:http pattern="/devicecodeMFA/**"
+				   use-expressions="true"
+				   entry-point-ref="oauthAuthenticationEntryPoint"
+				   create-session="stateless"
+				   authentication-manager-ref="clientAuthenticationManager">
+		<security:http-basic entry-point-ref="oauthAuthenticationEntryPoint" />
+		<!-- include this only if you need to authenticate clients via request parameters -->
+		<security:custom-filter ref="clientAssertionEndpointFilter" after="PRE_AUTH_FILTER" /> <!-- this one has to go first -->
+		<security:custom-filter ref="clientCredentialsEndpointFilter" after="BASIC_AUTH_FILTER" />
+		<security:custom-filter ref="corsFilter" after="SECURITY_CONTEXT_FILTER" />
+		<security:access-denied-handler ref="oauthAccessDeniedHandler" />
+		<security:csrf disabled="true"/>
+	</security:http>
 
 </beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml
similarity index 98%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml
index 592d56a2e..4cbd44c60 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml
@@ -34,7 +34,7 @@
 	</bean>
 
 	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
-		<property name="packagesToScan" value="org.mitre" />
+		<property name="packagesToScan" value="cz.muni.ics" />
 		<property name="persistenceProviderClass" value="org.eclipse.persistence.jpa.PersistenceProvider" />
 		<property name="dataSource" ref="dataSource" />
 		<property name="jpaVendorAdapter" ref="jpaAdapter" />
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/local-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/local-config.xml
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/local-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/local-config.xml
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/locale-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/locale-config.xml
similarity index 84%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/locale-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/locale-config.xml
index 60cdb6b0f..7f6e95dc7 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/locale-config.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/locale-config.xml
@@ -19,11 +19,11 @@
 	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
-	<bean id="messageSource" class="org.mitre.openid.connect.config.JsonMessageSource">
+	<bean id="messageSource" class="cz.muni.ics.openid.connect.config.JsonMessageSource">
 		<property name="baseDirectory" value="/resources/js/locale/" />
 		<property name="useCodeAsDefaultMessage" value="true" />
 	</bean>
 
-	<bean id="localeResolver" class="org.mitre.openid.connect.config.ConfigurationBeanLocaleResolver" />
+	<bean id="localeResolver" class="cz.muni.ics.openid.connect.config.ConfigurationBeanLocaleResolver" />
 
 </beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/user-context.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/server-config.xml
similarity index 63%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/user-context.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/server-config.xml
index 86e4be23c..e28024d45 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/user-context.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/server-config.xml
@@ -30,29 +30,18 @@
 		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
 		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
 
-	<security:authentication-manager id="authenticationManager">
-		<security:authentication-provider>
-			<security:jdbc-user-service data-source-ref="dataSource"/>
-		</security:authentication-provider>
-	</security:authentication-manager>
-		
-	<mvc:view-controller path="/login" view-name="login" />
-
-	<security:http authentication-manager-ref="authenticationManager"> 
+	<bean id="configBean" class="cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean">
+		<!-- This property sets the root URL of the server, known as the issuer -->
+		<property name="issuer" value="${main.oidc.issuer.url}" />
+		<!-- This property is a URL pointing to a logo image 24px high to be used in the top bar -->
+		<property name="logoImageUrl" value="${logo.image.url}" />
+		<!-- This property sets the display name of the server, displayed in the topbar and page title -->
+		<property name="topbarTitle" value="${topbar.title}" />
+		<!-- This property forces the issuer value to start with "https", recommended on production servers -->
+		<property name="forceHttps" value="true" />
+		<!-- This property allows the server to create and accept fully-composed
+			 user URIs (with the user-code emebedded) for the device flow -->
+		<property name="allowCompleteDeviceCodeUri" value="true" />
+	</bean>
 	
-		<security:intercept-url pattern="/authorize" access="hasRole('ROLE_USER')" />
-		<security:intercept-url pattern="/**" access="permitAll" />
-		
-		<security:form-login login-page="/login" authentication-failure-url="/login?error=failure" authentication-success-handler-ref="authenticationTimeStamper" />
-		<security:custom-filter ref="authRequestFilter" after="SECURITY_CONTEXT_FILTER" />
-		<security:logout logout-url="/logout" />
-		<security:anonymous />
-		<security:expression-handler ref="oauthWebExpressionHandler" />
-		<security:headers>
-			<security:frame-options policy="DENY" />
-		</security:headers>
-		<security:csrf />
-		<security:custom-filter ref="mdcFilter" before="FIRST"/>
-	</security:http>	
-
 </beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/spring-servlet.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/spring-servlet.xml
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/spring-servlet.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/spring-servlet.xml
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/actionmenu.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/actionmenu.tag
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/actionmenu.tag
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/actionmenu.tag
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/bbmri/footer.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/bbmri/footer.tag
new file mode 100644
index 000000000..e6d160f0b
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/bbmri/footer.tag
@@ -0,0 +1,25 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ attribute name="js" required="false"%>
+<%@ attribute name="baseURL" required="true"%>
+<%@ attribute name="samlResourcesURL" required="true"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+<jsp:useBean id="date" class="java.util.Date" />
+
+<div id="footer">
+    <div style="margin: 0px auto; max-width: 1000px;">
+        <div style="float: left;">
+            <img src="${samlResourcesURL}/module.php/bbmri/res/img/BBMRI-ERIC-gateway-for-health_216.png" alt="BBMRI-ERIC Logo">
+        </div>
+
+        <div style="float: left;">
+            <p>BBMRI-ERIC, Neue Stiftingtalstrasse 2/B/6, 8010 Graz, Austria
+                &nbsp; &nbsp; +43 316 34 99 17-0 &nbsp;
+                <a href="mailto:contact@bbmri-eric.eu">contact@bbmri-eric.eu</a>
+            </p>
+            <p>Copyright &copy; BBMRI-ERIC <fmt:formatDate value="${date}" pattern="yyyy" /></p>
+        </div>
+    </div>
+</div><!-- #footer -->
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/bbmri/header.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/bbmri/header.tag
new file mode 100644
index 000000000..aeb0a8b7e
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/bbmri/header.tag
@@ -0,0 +1,22 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags/common" %>
+<%@ attribute name="title" required="true" %>
+<%@ attribute name="reqURL" required="true" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="samlResourcesURL" required="true" %>
+<%@ attribute name="cssLinks" required="true" type="java.util.ArrayList<java.lang.String>" %>
+
+<c:set var="logoURL" value="${samlResourcesURL}/module.php/bbmri/res/img/BBMRI-ERIC-gateway-for-health_430.png"/>
+
+<o:headerInit title="${title}" reqURL="${reqURL}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}" />
+
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/bbmri/res/bootstrap/css/bootstrap.min.css" />
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/bbmri/res/css/bbmri.css" />
+
+<o:headerCssLinks cssLinks="${cssLinks}"/>
+
+</head>
+
+<o:headerBody logoURL="${logoURL}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/ceitec/footer.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/ceitec/footer.tag
new file mode 100644
index 000000000..124fc6fb0
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/ceitec/footer.tag
@@ -0,0 +1,26 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ attribute name="js" required="false"%>
+<%@ attribute name="baseURL" required="true"%>
+<%@ attribute name="samlResourcesURL" required="true"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+<jsp:useBean id="date" class="java.util.Date" />
+
+<c:set var="issuer" value="${config.issuer}" />
+
+<div id="footer">
+    <div style="margin: 0px auto; max-width: 1000px;">
+        <div style="float: left;">
+            <img src="${samlResourcesURL}/module.php/ceitec/res/img/logo_64.png" alt="CEITEC Logo">
+        </div>
+        <div style="float: left;">
+            <p>CEITEC, Masaryk University, Žerotínovo nám. 9, 601 77 Brno, Czech Republic
+                &nbsp; &nbsp; +420 549 498 732 &nbsp;
+                <a href="mailto:is.ceitec@ceitec.cz">is.ceitec@ceitec.cz</a>
+            </p>
+            <p>Copyright &copy; CEITEC <fmt:formatDate value="${date}" pattern="yyyy" /></p>
+        </div>
+    </div>
+</div>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/ceitec/header.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/ceitec/header.tag
new file mode 100644
index 000000000..4214110df
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/ceitec/header.tag
@@ -0,0 +1,22 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags/common" %>
+<%@ attribute name="title" required="true" %>
+<%@ attribute name="reqURL" required="true" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="samlResourcesURL" required="true" %>
+<%@ attribute name="cssLinks" required="true" type="java.util.ArrayList<java.lang.String>" %>
+
+<c:set var="logoURL" value="${samlResourcesURL}/module.php/ceitec/res/img/logo_512.png"/>
+
+<o:headerInit title="${title}" reqURL="${reqURL}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/ceitec/res/bootstrap/css/bootstrap.min.css" />
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/ceitec/res/css/ceitec.css" />
+
+<o:headerCssLinks cssLinks="${cssLinks}"/>
+
+</head>
+
+<o:headerBody logoURL="${logoURL}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/cesnet/footer.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/cesnet/footer.tag
new file mode 100644
index 000000000..e84472bb5
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/cesnet/footer.tag
@@ -0,0 +1,50 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ attribute name="js" required="false"%>
+<%@ attribute name="baseURL" required="true"%>
+<%@ attribute name="samlResourcesURL" required="true"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+<jsp:useBean id="date" class="java.util.Date" />
+
+<div id="footer">
+    <footer>
+        <div class="container">
+            <div class="row">
+                <div class="col-md-4 logo">
+                    <a href="http://www.cesnet.cz/">
+                        <img src="${samlResourcesURL}/module.php/cesnet/res/img/logo-cesnet.png" alt="CESNET logo" style="width: 250px;">
+                    </a>
+                </div>
+                <div class="col-md-8">
+                    <div class="row">
+                        <div class="col col-sm-6">
+                            <h2>${langProps['footer_other_projects']}</h2>
+                            <ul>
+                                <li><a href="http://www.cesnet.cz/wp-content/uploads/2014/04/CzechLight-family_Posp%C3%ADchal.pdf">CzechLight</a></li>
+                                <li><a href="http://www.ultragrid.cz/en">UltraGrid</a></li>
+                                <li><a href="http://www.4kgateway.com/">4k Gateway</a></li>
+                                <li><a href="http://shongo.cesnet.cz/">Shongo</a></li>
+                                <li><a href="http://www.cesnet.cz/sluzby/sledovani-provozu-site/sledovani-infrastruktury/">FTAS a G3</a></li>
+                                <li><a href="https://www.liberouter.org/">Librerouter</a></li>
+                            </ul>
+                        </div>
+                        <div class="col col-sm-6">
+                            <h2>${langProps['footer_helpdesk']}</h2>
+                            TEL: +420 224 352 994<br>
+                            GSM: +420 602 252 531<br>
+                            FAX: +420 224 313 211<br>
+                            <a href="mailto:perun@cesnet.cz">perun@cesnet.cz</a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="col col-sm-12 copyright">
+                    &copy; 1991–<fmt:formatDate value="${date}" pattern="yyyy" /> | CESNET, z. s. p. o.
+                </div>
+            </div>
+        </div>
+    </footer>
+</div>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/cesnet/header.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/cesnet/header.tag
new file mode 100644
index 000000000..e25dce99c
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/cesnet/header.tag
@@ -0,0 +1,22 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags/common" %>
+<%@ attribute name="title" required="true" %>
+<%@ attribute name="reqURL" required="true" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="samlResourcesURL" required="true" %>
+<%@ attribute name="cssLinks" required="true" type="java.util.ArrayList<java.lang.String>" %>
+
+<c:set var="logoURL" value="${samlResourcesURL}/module.php/cesnet/res/img/cesnet_RGB.png"/>
+
+<o:headerInit title="${title}" reqURL="${reqURL}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}" />
+
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/cesnet/res/bootstrap/css/bootstrap.min.css" />
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/cesnet/res/css/cesnet.css" />
+
+<o:headerCssLinks cssLinks="${cssLinks}"/>
+
+</head>
+
+<o:headerBody logoURL="${logoURL}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/attributesConsent.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/attributesConsent.tag
new file mode 100644
index 000000000..150501da8
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/attributesConsent.tag
@@ -0,0 +1,82 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true"
+        import="cz.muni.ics.oidc.server.elixir.GA4GHClaimSource" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags" %>
+
+<c:if test="${empty scopes}">
+    <p>${langProps['no_scopes']}</p>
+</c:if>
+<c:if test="${not empty scopes}">
+    <ul id="perun-table_with_attributes" class="perun-attributes">
+        <c:forEach var="scope" items="${scopes}">
+            <c:set var="scopeValue" value="${langProps[scope.value]}"/>
+            <c:if test="${empty fn:trim(scopeValue)}">
+                <c:set var="scopeValue" value="${scope.value}"/>
+            </c:if>
+            <c:set var="singleClaim" value="${fn:length(claims[scope.value]) eq 1}" />
+            <li class="scope-item scope_${fn:escapeXml(scope.value)} ${' '} ${fn:length(claims[scope.value]) eq 0 ? 'hidden' : ''}">
+                <div class="row">
+                    <div class="col-sm-5">
+                        <div class="checkbox-wrapper">
+                            <input class="mt-0 mr-half" type="checkbox" name="scope_${ fn:escapeXml(scope.value) }" checked="checked"
+                                   id="scope_${fn:escapeXml(scope.value)}" value="${fn:escapeXml(scope.value)}">
+                        </div>
+                        <h2 class="perun-attrname <c:out value="${classes['perun-attrname.h2.class']}"/>">
+                            <label for="scope_${fn:escapeXml(scope.value)}"
+                                   class="<c:out value="${classes['perun-attrname.h2.class']}"/>">${scopeValue}</label>
+                        </h2>
+                    </div>
+                    <div class="perun-attrcontainer col-sm-7">
+                        <span class="perun-attrvalue">
+                            <ul class="perun-attrlist <c:out value="${classes['perun-attrcontainer.ul.class']}"/>">
+                                <c:forEach var="claim" items="${claims[scope.value]}">
+                                    <c:choose>
+                                        <c:when test="${not singleClaim}">
+                                            <li class="subclaim subclaim_${fn:escapeXml(claim.key)}">
+                                                <c:set var="claimKey" value="${langProps[claim.key]}"/>
+                                                <c:if test="${empty fn:trim(claimKey)}">
+                                                    <c:set var="claimKey" value="${claim.key}"/>
+                                                </c:if>
+                                                <h3 class="visible-xs-block visible-sm-inline-block visible-md-inline-block
+                                                    visible-lg-inline-block <c:out value="${classes['perun-attrlist.h3.class']}"/>">
+                                                    ${claimKey}:
+                                                </h3>
+                                                <c:if test="${claim.value.getClass().name eq 'java.util.ArrayList'}">
+                                                    <ul class="subclaim-value">visible-md-inline-block
+                                                        <c:forEach var="subValue" items="${claim.value}">
+                                                            <li>${subValue}</li>
+                                                        </c:forEach>
+                                                    </ul>
+                                                </c:if>
+                                                <c:if test="${not(claim.value.getClass().name eq 'java.util.ArrayList')}">
+                                                    <span class="subclaim-value">${claim.value}</span>
+                                                </c:if>
+                                            </li>
+                                        </c:when>
+                                        <c:when test="${claim.value.getClass().name eq 'java.util.ArrayList'}">
+                                            <c:forEach var="subValue" items="${claim.value}">
+                                                <c:choose>
+                                                    <c:when test="${claim.key=='ga4gh_passport_v1'}">
+                                                        <li><%= GA4GHClaimSource.parseAndVerifyVisa(
+                                                                (String) jspContext.findAttribute("subValue")).getPrettyString() %></li>
+                                                    </c:when>
+                                                    <c:otherwise>
+                                                        <li>${subValue}</li>
+                                                    </c:otherwise>
+                                                </c:choose>
+                                            </c:forEach>
+                                        </c:when>
+                                        <c:otherwise>
+                                            <li>${claim.value}</li>
+                                        </c:otherwise>
+                                    </c:choose>
+                                </c:forEach>
+                            </ul>
+                        </span>
+                    </div>
+                </div>
+            </li>
+        </c:forEach>
+    </ul>
+</c:if>
\ No newline at end of file
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/consentButtons.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/consentButtons.tag
new file mode 100644
index 000000000..9b65cc499
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/consentButtons.tag
@@ -0,0 +1,22 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags" %>
+
+<div class="row">
+    <div class="col-sm-6">
+        <div id="yesform">
+            <button id="yesbutton" name="yes" type="submit" class="btn btn-success btn-lg btn-block btn-primary"
+                    onclick="$('#user_oauth_approval').attr('value', true);">
+                <span>${langProps['yes']}</span>
+            </button>
+        </div>
+    </div>
+    <div class="col-sm-6">
+        <div>
+            <button id="nobutton" name="no" type="submit" class="btn btn-lg btn-default btn-block btn-no"
+                    onclick="$('#user_oauth_approval').attr('value', false);">
+                <span>${langProps['no']}</span>
+            </button>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/footer.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/footer.tag
new file mode 100644
index 000000000..7a9a97f96
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/footer.tag
@@ -0,0 +1,38 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags" %>
+<%@ taglib prefix="elixir" tagdir="/WEB-INF/tags/elixir" %>
+<%@ taglib prefix="cesnet" tagdir="/WEB-INF/tags/cesnet" %>
+<%@ taglib prefix="bbmri" tagdir="/WEB-INF/tags/bbmri" %>
+<%@ taglib prefix="ceitec" tagdir="/WEB-INF/tags/ceitec" %>
+<%@ taglib prefix="europdx" tagdir="/WEB-INF/tags/europdx" %>
+<%@ taglib prefix="muni" tagdir="/WEB-INF/tags/muni" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="theme" required="true" %>
+
+<c:choose>
+    <c:when test="${theme eq 'elixir'}">
+        <elixir:footer baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'cesnet'}">
+        <cesnet:footer baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'bbmri'}">
+        <bbmri:footer baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'ceitec'}">
+        <ceitec:footer baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'europdx'}">
+        <europdx:footer baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'muni'}">
+        <muni:footer/>
+    </c:when>
+    <c:otherwise>
+        <o:footer />
+    </c:otherwise>
+</c:choose>
+
+<script type="text/javascript" src="resources/js/jquery-3-3-1.min.js"></script>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/header.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/header.tag
new file mode 100644
index 000000000..7814e0936
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/header.tag
@@ -0,0 +1,38 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags" %>
+<%@ taglib prefix="elixir" tagdir="/WEB-INF/tags/elixir" %>
+<%@ taglib prefix="cesnet" tagdir="/WEB-INF/tags/cesnet" %>
+<%@ taglib prefix="bbmri" tagdir="/WEB-INF/tags/bbmri" %>
+<%@ taglib prefix="ceitec" tagdir="/WEB-INF/tags/ceitec" %>
+<%@ taglib prefix="europdx" tagdir="/WEB-INF/tags/europdx" %>
+<%@ taglib prefix="muni" tagdir="/WEB-INF/tags/muni" %>
+<%@ attribute name="title" required="true" %>
+<%@ attribute name="reqURL" required="true" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="theme" required="true" %>
+<%@ attribute name="cssLinks" required="true" type="java.util.ArrayList<java.lang.String>" %>
+
+<c:choose>
+    <c:when test="${theme eq 'elixir'}">
+        <elixir:header title="${title}" reqURL="${reqURL}" cssLinks="${cssLinks}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'cesnet'}">
+        <cesnet:header title="${title}" reqURL="${reqURL}" cssLinks="${cssLinks}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'bbmri'}">
+        <bbmri:header title="${title}" reqURL="${reqURL}" cssLinks="${cssLinks}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'ceitec'}">
+        <ceitec:header title="${title}" reqURL="${reqURL}" cssLinks="${cssLinks}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'europdx'}">
+        <europdx:header title="${title}" reqURL="${reqURL}" cssLinks="${cssLinks}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:when test="${theme eq 'muni'}">
+        <muni:header title="${title}" reqURL="${reqURL}" cssLinks="${cssLinks}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+    </c:when>
+    <c:otherwise>
+        <o:header title="${title}"/>
+    </c:otherwise>
+</c:choose>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerBody.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerBody.tag
new file mode 100644
index 000000000..6c6df4df3
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerBody.tag
@@ -0,0 +1,14 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags/common" %>
+<%@ attribute name="logoURL" required="true" %>
+
+<body>
+
+    <div id="wrap">
+        <c:if test="${ langsMap.size() > 1 }">
+            <o:langbar lang="${lang}" langsMap="${langsMap}" reqURL="${reqURL}"/>
+        </c:if>
+        <div id="header">
+            <img src="${logoURL}" alt="logo">
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerCssLinks.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerCssLinks.tag
new file mode 100644
index 000000000..23666ade5
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerCssLinks.tag
@@ -0,0 +1,7 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ attribute name="cssLinks" required="true" type="java.util.ArrayList<java.lang.String>" %>
+
+<c:forEach var="link" items="${cssLinks}">
+    <link rel="stylesheet" type="text/css" href="${link}" />
+</c:forEach>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerInit.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerInit.tag
new file mode 100644
index 000000000..7060d202d
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/headerInit.tag
@@ -0,0 +1,22 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ attribute name="title" required="true" %>
+<%@ attribute name="reqURL" required="true" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="samlResourcesURL" required="true" %>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="${lang}" xml:lang="${lang}">
+
+<head>
+
+    <base href="${config.issuer}">
+    <title>${config.topbarTitle} - ${title}</title>
+
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0" />
+    <meta name="robots" content="noindex, nofollow" />
+
+    <link rel="stylesheet" type="text/css" href="${samlResourcesURL}/resources/default.css" />
+    <link rel="stylesheet" type="text/css" href="resources/css/customs.css">
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/langbar.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/langbar.tag
new file mode 100644
index 000000000..89df64744
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/common/langbar.tag
@@ -0,0 +1,34 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ attribute name="lang" required="true" %>
+<%@ attribute name="langsMap" required="true" type="java.util.Map" %>
+<%@ attribute name="reqURL" required="true" %>
+
+<c:set var="i" value="0"/>
+<div id="languagebar_line">
+    <div id="languagebar">
+    <c:choose>
+        <c:when test="${fn:contains(reqURL, '?')}">
+            <c:set var="requestURL" value="${reqURL}${'&lang='}"/>
+        </c:when>
+        <c:otherwise>
+            <c:set var="requestURL" value="${reqURL}${'?lang='}"/>
+        </c:otherwise>
+    </c:choose>
+        <c:forEach var="langEntry" items="${langsMap}">
+            <c:choose>
+                <c:when test="${ langEntry.key.equalsIgnoreCase(lang)}">
+                    <c:out value="${langEntry.value}" />
+                </c:when>
+                <c:otherwise>
+                    <a href="${requestURL}${langEntry.key}">${langEntry.value}</a>
+                </c:otherwise>
+            </c:choose>
+            <c:if test="${ i < (langsMap.size() - 1) }">
+                <c:out value=" | "/>
+            </c:if>
+            <c:set var="i" value="${ i + 1 }"/>
+        </c:forEach>
+    </div>
+</div>
\ No newline at end of file
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag
new file mode 100644
index 000000000..605ff4616
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag
@@ -0,0 +1,11 @@
+<%@ tag pageEncoding="UTF-8" import="cz.muni.ics.oidc.server.configurations.PerunOidcConfig" trimDirectiveWhitespaces="true" %>
+<%@ tag import="org.springframework.web.context.support.WebApplicationContextUtils" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<c:if test="${ config.heartMode }"><span class="pull-left"><img src="resources/images/heart_mode.png" alt="HEART Mode" title="This server is running in HEART Compliance Mode" /></span> </c:if>
+<%
+    PerunOidcConfig perunOidcConfig = WebApplicationContextUtils.getWebApplicationContext(application).getBean("perunOidcConfig", PerunOidcConfig.class);
+%>
+Powered by
+<a href="https://github.com/CESNET/perun-mitreid">Perun MITREid</a> <span class="label"><%=perunOidcConfig.getPerunOIDCVersion()%></span>
+<span class="pull-right">&copy; 2017 The MIT Internet Trust Consortium.</span>.
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/elixir/footer.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/elixir/footer.tag
new file mode 100644
index 000000000..d0d11980e
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/elixir/footer.tag
@@ -0,0 +1,27 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ attribute name="js" required="false"%>
+<%@ attribute name="baseURL" required="true"%>
+<%@ attribute name="samlResourcesURL" required="true"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+<jsp:useBean id="date" class="java.util.Date" />
+
+<div id="footer">
+    <div style="margin: 0 auto; max-width: 1000px;">
+        <div style="float: left;">
+            <img src="${samlResourcesURL}/module.php/elixir/res/img/logo_64.png" alt="ELIXIR Logo">
+        </div>
+        <div style="float: left;">
+            <p>ELIXIR, Welcome Trust Genome Campus, Hinxton, Cambridgeshire, CB10 1SD, UK&nbsp; &nbsp; +44&nbsp;(0)1223&nbsp;492-670&nbsp;&nbsp;
+                <a href="mailto:info@elixir-europe.org">info@elixir-europe.org</a>
+            </p>
+            <p>Copyright &copy; ELIXIR <fmt:formatDate value="${date}" pattern="yyyy" /> |
+                <a href="https://www.elixir-europe.org/legal/privacy">Privacy</a> |
+                <a href="https://www.elixir-europe.org/legal/cookies">Cookies</a> |
+                <a href="https://www.elixir-europe.org/legal/terms-of-use">Terms of use</a>
+            </p>
+        </div>
+    </div>
+</div>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/elixir/header.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/elixir/header.tag
new file mode 100644
index 000000000..33260a391
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/elixir/header.tag
@@ -0,0 +1,22 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags/common" %>
+<%@ attribute name="title" required="true" %>
+<%@ attribute name="reqURL" required="true" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="samlResourcesURL" required="true" %>
+<%@ attribute name="cssLinks" required="true" type="java.util.ArrayList<java.lang.String>" %>
+
+<c:set var="logoURL" value="${samlResourcesURL}/module.php/elixir/res/img/logo_256.png"/>
+
+<o:headerInit title="${title}" reqURL="${reqURL}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}"/>
+
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/elixir/res/bootstrap/css/bootstrap.min.css" />
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/elixir/res/css/elixir.css" />
+
+<o:headerCssLinks cssLinks="${cssLinks}"/>
+
+</head>
+
+<o:headerBody logoURL="${logoURL}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/europdx/footer.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/europdx/footer.tag
new file mode 100644
index 000000000..14fd3f1c7
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/europdx/footer.tag
@@ -0,0 +1,35 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ attribute name="js" required="false"%>
+<%@ attribute name="baseURL" required="true"%>
+<%@ attribute name="samlResourcesURL" required="true"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+<jsp:useBean id="date" class="java.util.Date" />
+
+<div id="footer">
+    <div class="row" style="margin: 0 auto; max-width: 1000px;">
+        <div class="col-md-6" style="float: left">
+            <img src="${samlResourcesURL}/module.php/europdx/res/img/eu_flag_128.png">
+            <p>The EDIReX project has received funding from the European Union’s Horizon 2020 research and innovation programme, grant agreement no. #731105</p>
+        </div>
+
+        <div class="col-md-6" style="float: right;">
+            <ul>
+                <li>
+                    <a href="http://www.twitter.com/EurOPDX"> Follow @EUROPDX</a>
+                </li>
+                <li>
+                    <a href="https://europdx.eu/#"> TERMS OF USE</a>
+                </li>
+            </ul>
+        </div>
+    </div>
+
+    <div class="row" style="text-align: center">
+        <div class="col-md-12 copyright">
+            <p> © 1991– 2019 | EuroPDX - <a href="mailto:contact@europdx.eu"> contact@europdx.eu </a></p>
+        </div>
+    </div>
+</div>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/europdx/header.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/europdx/header.tag
new file mode 100644
index 000000000..5866e05fe
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/europdx/header.tag
@@ -0,0 +1,22 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags/common" %>
+<%@ attribute name="title" required="true" %>
+<%@ attribute name="reqURL" required="true" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="samlResourcesURL" required="true" %>
+<%@ attribute name="cssLinks" required="true" type="java.util.ArrayList<java.lang.String>" %>
+
+<c:set var="logoURL" value="${samlResourcesURL}/module.php/europdx/res/img/europdx_logo.png"/>
+
+<o:headerInit title="${title}" reqURL="${reqURL}" baseURL="${baseURL}" samlResourcesURL="${samlResourcesURL}" />
+
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/europdx/res/bootstrap/css/bootstrap.min.css" />
+<link rel="stylesheet" type="text/css" href="${samlResourcesURL}/module.php/europdx/res/css/europdx.css" />
+
+<o:headerCssLinks cssLinks="${cssLinks}"/>
+
+</head>
+
+<o:headerBody logoURL="${logoURL}"/>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/header.tag
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/header.tag
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/muni/footer.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/muni/footer.tag
new file mode 100644
index 000000000..8d0bc5487
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/muni/footer.tag
@@ -0,0 +1,17 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+
+        </div>
+    </div>
+</main>
+
+<footer class="footer">
+    <div class="row-main">
+        <p class="footer__copyrights">
+            ${langProps['masaryk_university']}<br />
+            ${langProps['service']}${" "}<a href="https://it.muni.cz/sluzby/jednotne-prihlaseni-na-muni" target="_blank">${langProps['unified_login']}</a>${" "}${langProps['provided']}${" "}<a href="https://www.ics.muni.cz" target="_blank">${langProps['ics']}</a>
+        </p>
+    </div>
+</footer>
+
+</body>
+</html>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/muni/header.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/muni/header.tag
new file mode 100644
index 000000000..a425f3c3a
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/muni/header.tag
@@ -0,0 +1,84 @@
+<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags/common" %>
+<%@ attribute name="title" required="true" %>
+<%@ attribute name="reqURL" required="true" %>
+<%@ attribute name="baseURL" required="true" %>
+<%@ attribute name="samlResourcesURL" required="true" %>
+<%@ attribute name="cssLinks" required="true" type="java.util.ArrayList<java.lang.String>" %>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html class="no-js touch no-touch" lang="${langProps['other_lang']}">
+<head>
+    <meta cahrset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <meta name="author" content="Masarykova univerzita" />
+
+    <title>${langProps['unified_login']} | ${title}</title>
+
+    <link rel="stylesheet" type="text/css" href="https://id.muni.cz/simplesaml/module.php/muni/css/bootstrap.min.css">
+    <link rel="stylesheet" type="text/css" href="https://id.muni.cz/simplesaml/module.php/muni/css/style-ie.css?1.2">
+    <link rel="stylesheet" type="text/css" href="https://id.muni.cz/simplesaml/module.php/muni/css/style.css?1.2">
+    <link rel="stylesheet" type="text/css" href="https://id.muni.cz/simplesaml/module.php/muni/css/style2.css?1.2">
+
+    <style type="text/css">
+        .checkbox-wrapper {
+            float: left;
+        }
+        .attrname-formatter {
+            display: block;
+            margin-left: 2em !important;
+        }
+    </style>
+
+    <o:headerCssLinks cssLinks="${cssLinks}"/>
+
+</head>
+
+<body>
+    <c:set var="alternateURL" value="${reqURL}&lang=${langProps['other_lang']}"/>
+    <p class="menu-accessibility">
+        <a title="${langProps['go_to_login_title']}" accesskey="2" href="#main">
+            ${langProps['go_to_login_text']}
+        </a>
+    </p>
+    <div class="header u-mb-0">
+        <div class="row-main">
+            <div class="header__wrap">
+                <h1 class="header__logo">
+                    <img src="${samlResourcesURL}/module.php/${theme}/img/${langProps['img_name']}.png"
+                         width="${langProps['img_width']}" height="${langProps['img_height']}" alt="${langProps['muni_logo']}"/>
+                </h1>
+                <div class="header__side">
+                    <div class="menu-lang" role="navigation">
+                        <p class="menu-lang__selected">
+                            <a href="${alternateURL}" rel="alternate" hreflang="${langProps['other_lang']}"
+                               lang="${langProps['other_lang']}" class="menu-lang__selected__link">
+                                ${langProps['other_language']}
+                            </a>
+                        </p>
+                    </div>
+                    <nav class="menu-mobile" role="navigation">
+                        <div class="menu-mobile__wrap">
+                            <div class="row-main">
+                                <ul class="menu-mobile__list">
+                                    <li class="menu-mobile__item">
+                                        <a href="${alternateURL}" rel="alternate" hreflang="${langProps['other_lang']}"
+                                            class="menu-mobile__link menu-mobile__link--lang" lang="${langProps['other_lang']}">
+                                            ${langProps['other_language']}
+                                        </a>
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                    </nav>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- END MU HEADER -->
+    <main class="main">
+        <div class="box-hero box-hero--particles box-hero--login u-mb-0 u-pt-50">
+            <div class="row-main">
+                <div>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/navmenu.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/navmenu.tag
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/navmenu.tag
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/navmenu.tag
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/sidebar.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/sidebar.tag
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/sidebar.tag
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/sidebar.tag
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/topbar.tag b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/topbar.tag
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/topbar.tag
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/tags/topbar.tag
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/task-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/task-config.xml
similarity index 54%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/task-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/task-config.xml
index 2b7513328..4719b08e3 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/task-config.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/task-config.xml
@@ -22,17 +22,4 @@
 	xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
 		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
 
-	<!-- Configuration for scheduled tasks -->
-	<task:scheduler id="taskScheduler" pool-size="10" /> 
-	<task:executor id="taskExecutor" pool-size="5" /> 
-	<task:annotation-driven scheduler="taskScheduler" executor="taskExecutor" /> 
-	
-	<!-- Schedule the token service and approved site service to clear out expired tokens and sites every 5 minutes -->
-	<task:scheduled-tasks scheduler="taskScheduler">
-	    <task:scheduled ref="defaultOAuth2ProviderTokenService" method="clearExpiredTokens" fixed-delay="300000" initial-delay="600000"/>
-	    <task:scheduled ref="defaultApprovedSiteService" method="clearExpiredSites" fixed-delay="300000" initial-delay="600000"/>
-	    <task:scheduled ref="defaultOAuth2AuthorizationCodeService" method="clearExpiredAuthorizationCodes" fixed-delay="300000" initial-delay="600000"/>
-	    <task:scheduled ref="defaultDeviceCodeService" method="clearExpiredDeviceCodes" fixed-delay="300000" initial-delay="600000"/>
-	</task:scheduled-tasks>
-
 </beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/ui-config.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/ui-config.xml
similarity index 96%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/ui-config.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/ui-config.xml
index 99951d230..0dcd0250b 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/ui-config.xml
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/ui-config.xml
@@ -31,7 +31,7 @@
 
 <!-- This file allows you to define components to the UI -->
 
-	<bean class="org.mitre.openid.connect.config.UIConfiguration" id="uiConfiguration">
+	<bean class="cz.muni.ics.openid.connect.config.UIConfiguration" id="uiConfiguration">
 		<property name="jsFiles">
 			<set>
 				<value>resources/js/client.js</value>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/user-context.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/user-context.xml
new file mode 100644
index 000000000..77013435c
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/user-context.xml
@@ -0,0 +1,824 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Replaces (overlays) the same-name file from project org.server.openid-connect-server-webapp.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	   xmlns:security="http://www.springframework.org/schema/security"
+	   xmlns:context="http://www.springframework.org/schema/context"
+	   xmlns:aop="http://www.springframework.org/schema/aop"
+	   xmlns:mvc="http://www.springframework.org/schema/mvc"
+	   xsi:schemaLocation="http://www.springframework.org/schema/security
+		 http://www.springframework.org/schema/security/spring-security.xsd
+		 http://www.springframework.org/schema/beans
+		 http://www.springframework.org/schema/beans/spring-beans.xsd
+		 http://www.springframework.org/schema/context
+		 http://www.springframework.org/schema/context/spring-context.xsd
+		 http://www.springframework.org/schema/aop
+		 http://www.springframework.org/schema/aop/spring-aop.xsd
+		 http://www.springframework.org/schema/mvc
+		 http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
+
+	<context:property-placeholder properties-ref="nonOverwrittenAttributeProperties" ignore-unresolvable="true" order="0"/>
+	<context:property-placeholder properties-ref="userAttrMappingsProperties" ignore-unresolvable="true" order="1"/>
+	<context:property-placeholder properties-ref="facilityAttrMappingsProperties" ignore-unresolvable="true" order="2"/>
+	<context:property-placeholder properties-ref="groupAttrMappingsProperties" ignore-unresolvable="true" order="3"/>
+	<context:property-placeholder properties-ref="voAttrMappingsProperties" ignore-unresolvable="true" order="4"/>
+	<context:property-placeholder properties-ref="resourceAttrMappingsProperties" ignore-unresolvable="true" order="5"/>
+	<context:property-placeholder properties-ref="coreProperties" order="6"/>
+
+	<context:component-scan base-package="cz.muni.ics.oidc"/>
+
+	<aop:aspectj-autoproxy proxy-target-class="true"/>
+
+	<mvc:interceptors>
+		<mvc:interceptor>
+			<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
+			<mvc:mapping path="/**" />
+			<mvc:exclude-mapping path="/token**"/>
+			<mvc:exclude-mapping path="/resources/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.IsTestSpController).MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.AupController).URL}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_AUTHORIZATION}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_ENSURE_VO_MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_PROD_VOS_GROUPS}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_IN_TEST_VOS_GROUPS}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_NOT_LOGGED_IN}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedController).UNAPPROVED_SPECIFIC_MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_CONTINUE_MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_FORM_MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController).REGISTRATION_FORM_SUBMIT_MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.RegistrationController).CONTINUE_DIRECT_MAPPING}**" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.LogoutController).MAPPING_SUCCESS}" />
+			<mvc:exclude-mapping path="#{T(cz.muni.ics.oidc.web.controllers.LoginController).MAPPING_FAILURE}" />
+			<mvc:exclude-mapping path="/saml**" />
+			<!-- Inject the UserInfo into the response -->
+			<ref bean="userInfoInterceptor" />
+		</mvc:interceptor>
+		<mvc:interceptor>
+			<!-- Exclude APIs and other machine-facing endpoints from these interceptors -->
+			<mvc:mapping path="/**" />
+			<mvc:exclude-mapping path="/token**"/>
+			<mvc:exclude-mapping path="/resources/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.discovery.web.DiscoveryEndpoint).WELL_KNOWN_URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.ProtectedResourceRegistrationEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.UserInfoEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.openid.connect.web.RootController).API_URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.DeviceEndpoint).URL}/**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.IntrospectionEndpoint).URL}**" />
+			<mvc:exclude-mapping path="/#{T(cz.muni.ics.oauth2.web.RevocationEndpoint).URL}**" />
+			<!-- Inject the server configuration into the response -->
+			<ref bean="serverConfigInterceptor"/>
+		</mvc:interceptor>
+	</mvc:interceptors>
+
+	<!-- default config values, by default override in file /etc/perun/perun-mitreid.properties -->
+	<bean id="defaultCoreProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties">
+			<props>
+				<prop key="main.oidc.issuer.url">https://perun-dev.meta.zcu.cz/oidc/</prop>
+				<prop key="logo.image.url">resources/images/perun_24px.png</prop>
+				<prop key="topbar.title">Perun OIDC</prop>
+				<prop key="admins">3197,59835</prop>
+				<prop key="perun.adapter.callFallback">true</prop>
+				<prop key="fill.missing.user.attrs">true</prop>
+				<prop key="perun.adapter.primary">RPC</prop>
+				<!-- RPC -->
+				<prop key="perun.rpc.enabled">true</prop>
+				<prop key="perun.rpc.url">https://perun.elixir-czech.cz/krb/rpc</prop>
+				<prop key="perun.rpc.user">xxxxx</prop>
+				<prop key="perun.rpc.password">yyyyy</prop>
+				<prop key="perun.rpc.serializer">json</prop>
+				<!-- LDAP -->
+				<prop key="ldap.host">perun.cesnet.cz</prop>
+				<prop key="ldap.user">xxxxx</prop>
+				<prop key="ldap.password">yyyyyyy</prop>
+				<prop key="ldap.port">636</prop>
+				<prop key="ldap.starttls">true</prop>
+				<prop key="ldap.ssl">true</prop>
+				<prop key="ldap.password">yyyyyyy</prop>
+				<prop key="ldap.timeoutSecs">120</prop>
+				<prop key="ldap.baseDN">dc=perun,dc=cesnet,dc=cz</prop>
+				<prop key="ldap.allowUntrustedSsl">false</prop>
+				<!-- JDBC -->
+				<prop key="jdbc.driver">org.mariadb.jdbc.Driver</prop>
+				<prop key="jdbc.url">jdbc:mariadb://localhost:3306/oidc</prop>
+				<prop key="jdbc.user">oidc</prop>
+				<prop key="jdbc.password">oidc</prop>
+				<prop key="jdbc.platform">org.eclipse.persistence.platform.database.MySQLPlatform</prop>
+				<!-- SAML AUTH -->
+				<prop key="saml.entityID">https://login.cesnet.cz/oidc/</prop>
+				<prop key="saml.keystore.location">/etc/perun/perun-mitreid-saml-keystore.jks</prop>
+				<prop key="saml.keystore.password">pass</prop>
+				<prop key="saml.keystore.defaultKey">pass</prop>
+				<prop key="saml.keystore.defaultKeyPass">pass</prop>
+				<prop key="saml.idp.defaultIdpEntityId">https://login.cesnet.cz/idp/</prop>
+				<prop key="saml.idp.metadataLocation"/> <!-- i.e. /etc/perun/login-cesnet-metadata.xml -->
+				<prop key="saml.idp.metadataUrl"/> <!-- i.e. https://login.cesnet.cz/proxy/module.php/metadata -->
+				<prop key="saml.proxy.enabled">true</prop>
+				<prop key="saml.proxy.spEntityId">https://login.cesnet.cz/proxy/</prop>
+				<prop key="saml.acrs.reserverdPrefixes">urn:cesnet:</prop>
+				<prop key="saml.acrs.enableComparison">false</prop>
+				<!-- STATS JDBC -->
+				<prop key="stats.jdbc.url">jdbc:mariadb://localhost:3306/STATS</prop>
+				<prop key="stats.jdbc.user">user</prop>
+				<prop key="stats.jdbc.password">password</prop>
+				<!-- WEB INTERFACE -->
+				<prop key="web.theme">default</prop>
+				<prop key="web.langs">EN</prop> <!-- EN,CS,SK -->
+				<prop key="web.langs.customfiles.path">/etc/perun</prop>
+				<prop key="web.classes.path">/etc/perun/web/classes.properties</prop>
+				<prop key="web.baseURL">https://login.cesnet.cz/proxy</prop>
+				<prop key="email.contact">login@cesnet.cz</prop>
+				<!-- LOGIN -->
+				<prop key="idpFilters.askPerun.enabled">false</prop>
+				<prop key="registrar.url">https://perun-dev.cesnet.cz/allfed/registrar/</prop>
+				<prop key="proxy.extSource.name"/>
+				<prop key="proxy.base.url"/>
+				<prop key="proxy.login.url"/>
+				<prop key="proxy.logout.url"/>
+				<prop key="proxy.add_client_id_to_acrs">false</prop>
+				<!-- OIDC STUFF -->
+				<prop key="jwk">file:///etc/perun/perun-oidc-keystore.jwks</prop>
+				<prop key="id_token.scopes">openid,profile,email,phone,address</prop>
+				<prop key="custom.claims">organization,eppns</prop>
+				<prop key="accessTokenClaimsModifier">cz.muni.ics.oidc.server.PerunAccessTokenEnhancer.NoOpAccessTokenClaimsModifier</prop>
+				<prop key="force.regenerate.userinfo.custom.claims" />
+				<prop key="force.regenerate.userinfo.standard.claims" />
+				<!-- UES ATTRS -->
+				<prop key="ues.orgUrl.attr">urn:perun:ues:attribute-def:def:organizationURL</prop>
+				<prop key="ues.affiliations.attr">urn:perun:ues:attribute-def:def:affiliation</prop>
+				<!-- USERINFO MODIFIERS -->
+				<prop key="userInfo.modifiers"/>
+				<!-- REQUEST FILTERS -->
+				<prop key="filter.names">stats</prop>
+				<prop key="filter.stats.class">cz.muni.ics.oidc.server.filters.impl.ProxyStatisticsFilter</prop>
+				<prop key="filter.stats.idpNameAttributeName">sourceIdPName</prop>
+				<prop key="filter.stats.idpEntityIdAttributeName">sourceIdPEntityID</prop>
+				<prop key="filter.stats.statisticsTableName">statistics_per_user</prop>
+				<prop key="filter.stats.identityProvidersMapTableName">statistics_idp</prop>
+				<prop key="filter.stats.serviceProvidersMapTableName">statistics_sp</prop>
+			</props>
+		</property>
+	</bean>
+
+	<bean id="coreProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties" ref="defaultCoreProperties"/>
+		<property name="locations">
+			<list>
+				<!-- PASSED FROM POM.XML / MAVEN BUILD PROPS -->
+				<value>file://${config.location}/perun-mitreid.properties</value>
+			</list>
+		</property>
+		<property name="ignoreResourceNotFound" value="false"/>
+	</bean>
+
+	<bean id="samlProperties" class="cz.muni.ics.oidc.saml.SamlProperties">
+		<property name="entityID" value="${saml.idp.defaultIdpEntityId}"/>
+		<property name="keystoreLocation" value="${saml.keystore.location}"/>
+		<property name="keystorePassword" value="${saml.keystore.password}"/>
+		<property name="keystoreDefaultKey" value="${saml.keystore.defaultKey}"/>
+		<property name="keystoreDefaultKeyPassword" value="${saml.keystore.defaultKeyPass}"/>
+		<property name="defaultIdpEntityId" value="${saml.idp.defaultIdpEntityId}"/>
+		<property name="idpMetadataFile" value="${saml.idp.metadataLocation}"/>
+		<property name="idpMetadataUrl" value="${saml.idp.metadataUrl}"/>
+		<property name="acrReservedPrefixes" value="#{'${saml.acrs.reserverdPrefixes}'.split('\s*,\s*')}"/>
+	</bean>
+
+	<bean id="nonOverwrittenAttributeProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties">
+			<props>
+				<prop key="user.attribute_names.fixedList">openid_sub,profile_preferred_username,profile_given_name,profile_middle_name,profile_family_name,profile_name,profile_zoneinfo,profile_locale,email_email,address_address_formatted,phone_phone,aups</prop>
+				<prop key="facility.attribute_names.fixedList">checkGroupMembership,allowRegistration,registrationUrl,dynamicRegistration,clientId,voShortNames,wayfFilter,wayfEFilter,requestedAups,capabilities,testSp</prop>
+				<prop key="group.attribute_names.fixedList"/>
+				<prop key="vo.attribute_names.fixedList">aup</prop>
+				<prop key="resource.attribute_names.fixedList">capabilities</prop>
+			</props>
+		</property>
+	</bean>
+
+	<bean id="defaultUserAttrProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties">
+			<props>
+				<prop key="user.attribute_names.customList"/>
+				<!-- ATTRIBUTES MAPPINGS -->
+				<prop key="openid_sub.mapping.ldap">login;x-ns-einfraid-persistent-shadow</prop>
+				<prop key="openid_sub.mapping.rpc">urn:perun:user:attribute-def:core:id</prop>
+				<prop key="openid_sub.type">STRING</prop>
+				<prop key="profile_preferred_username.mapping.ldap">login;x-ns-einfra</prop>
+				<prop key="profile_preferred_username.mapping.rpc">urn:perun:user:attribute-def:def:login-namespace:einfra</prop>
+				<prop key="profile_preferred_username.type">STRING</prop>
+				<prop key="profile_given_name.mapping.ldap">givenName</prop>
+				<prop key="profile_given_name.mapping.rpc">urn:perun:user:attribute-def:core:firstName</prop>
+				<prop key="profile_given_name.type">STRING</prop>
+				<prop key="profile_middle_name.mapping.ldap">middleName</prop>
+				<prop key="profile_middle_name.mapping.rpc">urn:perun:user:attribute-def:core:middleName</prop>
+				<prop key="profile_middle_name.type">STRING</prop>
+				<prop key="profile_family_name.mapping.ldap">sn</prop>
+				<prop key="profile_family_name.mapping.rpc">urn:perun:user:attribute-def:core:lastName</prop>
+				<prop key="profile_family_name.type">STRING</prop>
+				<prop key="profile_name.mapping.ldap">displayName</prop>
+				<prop key="profile_name.mapping.rpc">urn:perun:user:attribute-def:core:displayName</prop>
+				<prop key="profile_name.type">STRING</prop>
+				<prop key="profile_zoneinfo.mapping.ldap">timezone</prop>
+				<prop key="profile_zoneinfo.mapping.rpc">urn:perun:user:attribute-def:def:timezone</prop>
+				<prop key="profile_zoneinfo.type">STRING</prop>
+				<prop key="profile_locale.mapping.ldap">preferredLanguage</prop>
+				<prop key="profile_locale.mapping.rpc">urn:perun:user:attribute-def:def:preferredLanguage</prop>
+				<prop key="profile_locale.type">STRING</prop>
+				<prop key="email_email.mapping.ldap">preferredMail</prop>
+				<prop key="email_email.mapping.rpc">urn:perun:user:attribute-def:def:preferredMail</prop>
+				<prop key="email_email.type">STRING</prop>
+				<prop key="phone_phone.mapping.ldap">telephoneNumber</prop>
+				<prop key="phone_phone.mapping.rpc">urn:perun:user:attribute-def:def:phone</prop>
+				<prop key="phone_phone.type">STRING</prop>
+				<prop key="address_address_formatted.mapping.ldap">postalAddress</prop>
+				<prop key="address_address_formatted.mapping.rpc">urn:perun:user:attribute-def:def:address</prop>
+				<prop key="address_address_formatted.type">STRING</prop>
+				<prop key="aups.mapping.ldap">aups</prop>
+				<prop key="aups.mapping.rpc">urn:perun:user:attribute-def:def:aups</prop>
+				<prop key="aups.type">MAP_KEY_VALUE</prop>
+				<prop key="aups.separator">=</prop>
+			</props>
+		</property>
+	</bean>
+
+	<bean id="userAttrMappingsProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties" ref="defaultUserAttrProperties" />
+		<property name="locations">
+			<list>
+				<!-- PASSED FROM POM.XML / MAVEN BUILD PROPS -->
+				<value>file://${config.location}/user-attribute-mappings.properties</value>
+			</list>
+		</property>
+		<property name="ignoreResourceNotFound" value="true"/>
+	</bean>
+
+	<bean id="defaultFacilityAttrProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties">
+			<props>
+				<prop key="facility.attribute_names.customList"/>
+				<!-- ATTRIBUTES MAPPINGS -->
+				<prop key="checkGroupMembership.mapping.ldap">checkGroupMembership</prop>
+				<prop key="checkGroupMembership.mapping.rpc">urn:perun:facility:attribute-def:def:checkGroupMembership</prop>
+				<prop key="checkGroupMembership.type">BOOLEAN</prop>
+				<prop key="allowRegistration.mapping.ldap">allowRegistration</prop>
+				<prop key="allowRegistration.mapping.rpc">urn:perun:facility:attribute-def:def:allowRegistration</prop>
+				<prop key="allowRegistration.type">BOOLEAN</prop>
+				<prop key="registrationUrl.mapping.ldap">registrationURL</prop>
+				<prop key="registrationUrl.mapping.rpc">urn:perun:facility:attribute-def:def:registrationURL</prop>
+				<prop key="registrationUrl.type">STRING</prop>
+				<prop key="dynamicRegistration.mapping.ldap">dynamicRegistration</prop>
+				<prop key="dynamicRegistration.mapping.rpc">urn:perun:facility:attribute-def:def:dynamicRegistration</prop>
+				<prop key="dynamicRegistration.type">BOOLEAN</prop>
+				<prop key="clientId.mapping.ldap">OIDCClientID</prop>
+				<prop key="clientId.mapping.rpc">urn:perun:facility:attribute-def:def:OIDCClientID</prop>
+				<prop key="clientId.type">STRING</prop>
+				<prop key="voShortNames.mapping.ldap">voShortNames</prop>
+				<prop key="voShortNames.mapping.rpc">urn:perun:facility:attribute-def:virt:voShortNames</prop>
+				<prop key="voShortNames.type">ARRAY</prop>
+				<prop key="wayfFilter.mapping.ldap">wayfFilter</prop>
+				<prop key="wayfFilter.mapping.rpc">urn:perun:facility:attribute-def:def:wayfFilter</prop>
+				<prop key="wayfFilter.type">STRING</prop>
+				<prop key="wayfEFilter.mapping.ldap">wayfEFilter</prop>
+				<prop key="wayfEFilter.mapping.rpc">urn:perun:facility:attribute-def:def:wayfEFilter</prop>
+				<prop key="wayfEFilter.type">STRING</prop>
+				<prop key="requestedAups.mapping.ldap">requiredAups</prop>
+				<prop key="requestedAups.mapping.rpc">urn:perun:facility:attribute-def:def:reqAups</prop>
+				<prop key="requestedAups.type">ARRAY</prop>
+				<prop key="capabilities.mapping.ldap">capabilities</prop>
+				<prop key="capabilities.mapping.rpc">urn:perun:facility:attribute-def:def:capabilities</prop>
+				<prop key="capabilities.type">ARRAY</prop>
+				<prop key="testSp.mapping.ldap">isTestSp</prop>
+				<prop key="testSp.mapping.rpc">urn:perun:facility:attribute-def:def:isTestSp</prop>
+				<prop key="testSp.type">BOOLEAN</prop>
+			</props>
+		</property>
+	</bean>
+
+	<bean id="facilityAttrMappingsProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties" ref="defaultFacilityAttrProperties" />
+		<property name="locations">
+			<list>
+				<!-- PASSED FROM POM.XML / MAVEN BUILD PROPS -->
+				<value>file://${config.location}/facility-attribute-mappings.properties</value>
+			</list>
+		</property>
+		<property name="ignoreResourceNotFound" value="true"/>
+	</bean>
+
+	<bean id="defaultGroupAttrProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties">
+			<props>
+				<prop key="group.attribute_names.customList"/>
+			</props>
+		</property>
+	</bean>
+
+	<bean id="groupAttrMappingsProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties" ref="defaultGroupAttrProperties" />
+		<property name="locations">
+			<list>
+				<!-- PASSED FROM POM.XML / MAVEN BUILD PROPS -->
+				<value>file://${config.location}/group-attribute-mappings.properties</value>
+			</list>
+		</property>
+		<property name="ignoreResourceNotFound" value="true"/>
+	</bean>
+
+	<bean id="defaultVoAttrProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties">
+			<props>
+				<prop key="vo.attribute_names.customList"/>
+				<!-- ATTRIBUTES MAPPINGS -->
+				<prop key="aup.mapping.ldap">aup</prop>
+				<prop key="aup.mapping.rpc">urn:perun:vo:attribute-def:def:aup</prop>
+				<prop key="aup.type">LARGE_STRING</prop>
+			</props>
+		</property>
+	</bean>
+
+	<bean id="voAttrMappingsProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties" ref="defaultVoAttrProperties" />
+		<property name="locations">
+			<list>
+				<!-- PASSED FROM POM.XML / MAVEN BUILD PROPS -->
+				<value>file://${config.location}/vo-attribute-mappings.properties</value>
+			</list>
+		</property>
+		<property name="ignoreResourceNotFound" value="true"/>
+	</bean>
+
+	<bean id="defaultResourceAttrProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties">
+			<props>
+				<prop key="resource.attribute_names.customList"/>
+				<!-- ATTRIBUTES MAPPINGS -->
+				<prop key="capabilities.mapping.ldap">capabilities</prop>
+				<prop key="capabilities.mapping.rpc">urn:perun:resource:attribute-def:def:capabilities</prop>
+				<prop key="capabilities.type">ARRAY</prop>
+			</props>
+		</property>
+	</bean>
+
+	<bean id="resourceAttrMappingsProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
+		<property name="properties" ref="defaultResourceAttrProperties" />
+		<property name="locations">
+			<list>
+				<!-- PASSED FROM POM.XML / MAVEN BUILD PROPS -->
+				<value>file://${config.location}/resource-attribute-mappings.properties</value>
+			</list>
+		</property>
+		<property name="ignoreResourceNotFound" value="true"/>
+	</bean>
+
+	<!-- defines our own user info service -->
+	<bean id="userInfoService" primary="true" class="cz.muni.ics.oidc.server.userInfo.PerunUserInfoService">
+		<property name="perunAdapter" ref="perunAdapter"/>
+		<property name="subAttribute" value="openid_sub"/>
+		<property name="preferredUsernameAttribute" value="profile_preferred_username"/>
+		<property name="givenNameAttribute" value="profile_given_name"/>
+		<property name="familyNameAttribute" value="profile_family_name"/>
+		<property name="middleNameAttribute" value="profile_middle_name"/>
+		<property name="fullNameAttribute" value="profile_name"/>
+		<property name="emailAttribute" value="email_email"/>
+		<property name="addressAttribute" value="address_address_formatted"/>
+		<property name="phoneAttribute" value="phone_phone"/>
+		<property name="zoneinfoAttribute" value="profile_zoneinfo"/>
+		<property name="localeAttribute" value="profile_locale"/>
+		<property name="properties" ref="coreProperties"/>
+		<property name="customClaimNames" value="#{'${custom.claims}'.split('\s*,\s*')}"/>
+		<property name="forceRegenerateUserinfoCustomClaims" value="#{'${force.regenerate.userinfo.custom.claims}'.split('\s*,\s*')}"/>
+		<property name="forceRegenerateUserinfoStandardClaims" value="#{'${force.regenerate.userinfo.standard.claims}'.split('\s*,\s*')}"/>
+	</bean>
+
+	<!-- replaces default translation service with our own for custom scope with custom claims. -->
+	<bean id="scopeClaimTranslator" primary="true" class="cz.muni.ics.oidc.server.PerunScopeClaimTranslationService">
+		<property name="perunUserInfoService" ref="userInfoService"/>
+	</bean>
+
+	<bean id="introspectionResultAssembler" class="cz.muni.ics.oidc.server.PerunIntrospectionResultAssembler" primary="true">
+		<constructor-arg name="configBean" ref="configBean"/>
+		<constructor-arg name="translator" ref="scopeClaimTranslator"/>
+	</bean>
+
+	<bean id="perunOidcConfig" class="cz.muni.ics.oidc.server.configurations.PerunOidcConfig">
+		<property name="rpcEnabled" value="${perun.rpc.enabled}"/>
+		<property name="rpcUrl" value="${perun.rpc.url}"/>
+		<property name="configBean" ref="configBean"/>
+		<property name="jwk" value="${jwk}"/>
+		<property name="jdbcUrl" value="${jdbc.url}"/>
+		<property name="theme" value="${web.theme}"/>
+		<property name="samlLoginURL" value="${proxy.login.url}"/>
+		<property name="samlLogoutURL" value="${proxy.logout.url}"/>
+		<property name="samlResourcesURL" value="${proxy.base.url}"/>
+		<property name="baseURL" value="${web.baseURL}"/>
+		<property name="registrarUrl" value="${registrar.url}"/>
+		<property name="fillMissingUserAttrs" value="${fill.missing.user.attrs}"/>
+		<property name="askPerunForIdpFiltersEnabled" value="${idpFilters.askPerun.enabled}"/>
+		<property name="proxyExtSourceName" value="${proxy.extSource.name}"/>
+		<property name="idTokenScopes" value="#{'${id_token.scopes}'.split('\s*,\s*')}"/>
+		<property name="availableLangs" value="#{'${web.langs}'.split('\s*,\s*')}"/>
+		<property name="localizationFilesPath" value="${web.langs.customfiles.path}"/>
+		<property name="webClassesFilePath" value="${web.classes.path}"/>
+		<property name="emailContact" value="${email.contact}"/>
+		<property name="addClientIdToAcrs" value="${proxy.add_client_id_to_acrs}"/>
+	</bean>
+
+	<bean id="facilityAttrsConfig" class="cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig">
+		<property name="checkGroupMembershipAttr" value="checkGroupMembership" />
+		<property name="allowRegistrationAttr" value="allowRegistration" />
+		<property name="registrationURLAttr" value="registrationURL" />
+		<property name="dynamicRegistrationAttr" value="dynamicRegistration" />
+		<property name="voShortNamesAttr" value="voShortNames" />
+		<property name="wayfFilterAttr" value="wayfFilter" />
+		<property name="wayfEFilterAttr" value="wayfEFilter" />
+		<property name="testSpAttr" value="testSp" />
+	</bean>
+
+	<!-- authentication -->
+
+	<!--suppress SpringXmlModelInspection -->
+	<security:http auto-config="false"
+				   use-expressions="true"
+				   entry-point-ref="samlEntryPoint"
+				   create-session="always"
+				   authentication-manager-ref="authenticationManager">
+		<security:csrf disabled="true"/>
+		<security:intercept-url pattern="/saml/**" access="permitAll()"/>
+		<security:intercept-url pattern="/logout" access="permitAll()"/>
+		<security:intercept-url pattern="#{T(cz.muni.ics.oidc.web.controllers.LogoutController).MAPPING_SUCCESS}" access="permitAll()"/>
+		<security:intercept-url pattern="#{T(cz.muni.ics.oidc.web.controllers.LoginController).MAPPING_FAILURE}" access="permitAll()"/>
+		<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
+		<security:custom-filter ref="mdcMuFilter" before="FIRST"/>
+		<security:custom-filter ref="metadataGeneratorFilter" before="CHANNEL_FILTER"/>
+		<security:custom-filter ref="clearSessionFilter" after="CHANNEL_FILTER"/>
+		<security:custom-filter ref="samlFilter" before="CSRF_FILTER"/>
+		<security:custom-filter ref="samlFilter" after="BASIC_AUTH_FILTER"/>
+		<security:custom-filter ref="callPerunFiltersFilter" before="LAST"/>
+		<security:logout logout-url="/saml/logout"/>
+	</security:http>
+
+	<security:authentication-manager id="authenticationManager">
+		<security:authentication-provider ref="authenticationProvider"/>
+	</security:authentication-manager>
+
+	<bean id="mdcMuFilter" class="cz.muni.ics.oidc.server.filters.impl.MultiMDCFilter"/>
+
+	<!-- SAML -->
+
+	<bean id="clearSessionFilter" class="cz.muni.ics.oidc.saml.SamlInvalidateSessionFilter">
+		<constructor-arg name="pattern" value="/authorize**"/>
+		<constructor-arg name="oidcIssuer" value="${main.oidc.issuer.url}"/>
+		<constructor-arg name="idpEntityId" value="${saml.idp.defaultIdpEntityId}"/>
+		<constructor-arg name="proxyEnabled" value="${saml.proxy.enabled}"/>
+		<constructor-arg name="proxySpEntityId" value="${saml.proxy.spEntityId}"/>
+		<constructor-arg name="contextLogoutHandler" ref="logoutHandler"/>
+	</bean>
+	<bean id="samlDiscovery" class="org.springframework.security.saml.SAMLDiscovery">
+		<property name="contextProvider" ref="samlContextProvider"/>
+		<property name="samlEntryPoint" ref="samlEntryPoint"/>
+		<property name="metadata" ref="metadata"/>
+	</bean>
+
+	<bean id="successRedirectHandler" class="cz.muni.ics.oidc.saml.PerunSamlAuthenticationSuccessHandler">
+		<property name="defaultTargetUrl" value="#{T(cz.muni.ics.oidc.web.controllers.LoginController).MAPPING_SUCCESS}"/>
+	</bean>
+
+	<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
+		<property name="defaultFailureUrl" value="#{T(cz.muni.ics.oidc.web.controllers.LoginController).MAPPING_FAILURE}"/>
+		<property name="useForward" value="true"/>
+	</bean>
+
+	<bean id="successLogoutHandler" class="cz.muni.ics.oidc.saml.PerunOidcLogoutSuccessHandler">
+		<property name="defaultTargetUrl" value="#{T(cz.muni.ics.oidc.web.controllers.LogoutController).MAPPING_SUCCESS}"/>
+		<property name="targetUrlParameter" value="#{T(cz.muni.ics.oidc.server.filters.PerunFilterConstants).PARAM_TARGET}"/>
+	</bean>
+
+	<bean id="logoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
+		<property name="clearAuthentication" value="true"/>
+		<property name="invalidateHttpSession" value="true"/>
+	</bean>
+
+	<bean id="samlLogoutProcessingFilter" class="org.springframework.security.saml.SAMLLogoutProcessingFilter">
+		<constructor-arg name="logoutSuccessHandler" ref="successLogoutHandler"/>
+		<constructor-arg name="handlers" ref="logoutHandler"/>
+	</bean>
+
+	<bean id="samlLogoutFilter" class="org.springframework.security.saml.SAMLLogoutFilter">
+		<constructor-arg name="logoutSuccessHandler" ref="successLogoutHandler"/>
+		<constructor-arg name="localHandler" ref="logoutHandler"/>
+		<constructor-arg name="globalHandlers" ref="logoutHandler"/>
+	</bean>
+
+	<bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager">
+		<constructor-arg name="storeFile">
+			<bean class="org.springframework.core.io.FileSystemResource">
+				<constructor-arg name="path" value="${saml.keystore.location}"/>
+			</bean>
+		</constructor-arg>
+		<constructor-arg name="storePass" value="${saml.keystore.password}"/>
+		<constructor-arg name="passwords">
+			<map>
+				<entry key="${saml.keystore.defaultKey}" value="${saml.keystore.defaultKeyPass}"/>
+			</map>
+		</constructor-arg>
+		<constructor-arg name="defaultKey" value="${saml.keystore.defaultKey}"/>
+	</bean>
+
+	<bean id="extendedMetadata" class="org.springframework.security.saml.metadata.ExtendedMetadata">
+		<property name="idpDiscoveryEnabled" value="false"/>
+	</bean>
+
+	<bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
+		<constructor-arg name="generator">
+			<bean class="org.springframework.security.saml.metadata.MetadataGenerator">
+				<property name="includeDiscoveryExtension" value="false"/>
+				<property name="entityId" value="${saml.entityID}"/>
+				<property name="extendedMetadata" ref="extendedMetadata"/>
+				<property name="wantAssertionSigned" value="true"/>
+				<property name="requestSigned" value="true"/>
+			</bean>
+		</constructor-arg>
+		<property name="normalizeBaseUrl" value="true"/>
+	</bean>
+
+	<bean id="metadataDisplayFilter" class="org.springframework.security.saml.metadata.MetadataDisplayFilter"/>
+
+	<bean id="metadata" class="org.springframework.security.saml.metadata.CachingMetadataManager">
+		<property name="defaultIDP" value="${saml.idp.defaultIdpEntityId}"/>
+		<property name="refreshCheckInterval" value="60000"/>
+		<property name="refreshRequired" value="false"/>
+		<constructor-arg name="providers">
+			<list>
+				<ref bean="idpMetadata"/>
+			</list>
+		</constructor-arg>
+	</bean>
+
+	<bean id="parserPool" class="org.opensaml.xml.parse.StaticBasicParserPool" init-method="initialize"/>
+
+	<bean id="parserPoolHolder" class="org.springframework.security.saml.parser.ParserPoolHolder"/>
+
+	<bean id="processor" class="org.springframework.security.saml.processor.SAMLProcessorImpl">
+		<constructor-arg name="bindings">
+			<list>
+				<bean id="httpPostBinding" class="org.springframework.security.saml.processor.HTTPPostBinding">
+					<constructor-arg name="parserPool" ref="parserPool"/>
+					<constructor-arg name="velocityEngine" value="#{T(org.springframework.security.saml.util.VelocityFactory).getEngine()}"/>
+				</bean>
+				<bean id="httpRedirectDeflateBinding" class="org.springframework.security.saml.processor.HTTPRedirectDeflateBinding">
+					<constructor-arg name="parserPool" ref="parserPool"/>
+				</bean>
+			</list>
+		</constructor-arg>
+	</bean>
+
+	<bean id="samlWebSSOProcessingFilter" class="org.springframework.security.saml.SAMLProcessingFilter">
+		<property name="authenticationManager" ref="authenticationManager"/>
+		<property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
+		<property name="authenticationFailureHandler" ref="authenticationFailureHandler"/>
+	</bean>
+
+	<bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
+		<constructor-arg name="filterChains">
+			<list>
+				<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
+					<constructor-arg name="requestMatcher">
+						<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
+							<constructor-arg name="pattern"
+											 value="#{T(org.springframework.security.saml.metadata.MetadataDisplayFilter).FILTER_URL}/**"/>
+						</bean>
+					</constructor-arg>
+					<constructor-arg name="filters">
+						<list>
+							<ref bean="metadataDisplayFilter"/>
+						</list>
+					</constructor-arg>
+				</bean>
+				<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
+					<constructor-arg name="requestMatcher">
+						<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
+							<constructor-arg name="pattern"
+											 value="#{T(org.springframework.security.saml.SAMLProcessingFilter).FILTER_URL}"/>
+						</bean>
+					</constructor-arg>
+					<constructor-arg name="filters">
+						<list>
+							<ref bean="samlWebSSOProcessingFilter"/>
+						</list>
+					</constructor-arg>
+				</bean>
+				<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
+					<constructor-arg name="requestMatcher">
+						<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
+							<constructor-arg name="pattern"
+											 value="#{T(org.springframework.security.saml.SAMLDiscovery).FILTER_URL}"/>
+						</bean>
+					</constructor-arg>
+					<constructor-arg name="filters">
+						<list>
+							<ref bean="samlDiscovery"/>
+						</list>
+					</constructor-arg>
+				</bean>
+				<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
+					<constructor-arg name="requestMatcher">
+						<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
+							<constructor-arg name="pattern"
+											 value="#{T(org.springframework.security.saml.SAMLEntryPoint).FILTER_URL}"/>
+						</bean>
+					</constructor-arg>
+					<constructor-arg name="filters">
+						<list>
+							<ref bean="samlEntryPoint"/>
+						</list>
+					</constructor-arg>
+				</bean>
+				<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
+					<constructor-arg name="requestMatcher">
+						<bean class="org.springframework.security.web.util.matcher.OrRequestMatcher">
+							<constructor-arg name="requestMatchers">
+								<list>
+									<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
+										<constructor-arg name="pattern"
+														 value="#{T(org.springframework.security.saml.SAMLLogoutFilter).FILTER_URL}"/>
+									</bean>
+									<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
+										<constructor-arg name="pattern" value="/logout"/>
+									</bean>
+								</list>
+							</constructor-arg>
+						</bean>
+					</constructor-arg>
+					<constructor-arg name="filters">
+						<list>
+							<ref bean="samlLogoutFilter"/>
+						</list>
+					</constructor-arg>
+				</bean>
+				<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
+					<constructor-arg name="requestMatcher">
+						<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
+							<constructor-arg name="pattern" value="#{T(org.springframework.security.saml.SAMLLogoutProcessingFilter).FILTER_URL}/**"/>
+						</bean>
+					</constructor-arg>
+					<constructor-arg name="filters">
+						<list>
+							<ref bean="samlLogoutProcessingFilter"/>
+						</list>
+					</constructor-arg>
+				</bean>
+			</list>
+		</constructor-arg>
+	</bean>
+
+	<bean id="webSSOProfileOptions" class="org.springframework.security.saml.websso.WebSSOProfileOptions">
+		<property name="includeScoping" value="false"/>
+	</bean>
+
+	<bean id="samlEntryPoint" class="cz.muni.ics.oidc.saml.PerunSamlEntryPoint">
+		<property name="defaultProfileOptions" ref="webSSOProfileOptions"/>
+	</bean>
+
+	<bean id="samlContextProvider" class="org.springframework.security.saml.context.SAMLContextProviderImpl"/>
+
+	<bean id="samlLogger" class="org.springframework.security.saml.log.SAMLDefaultLogger">
+		<property name="logMessagesOnException" value="true"/>
+		<property name="logErrors" value="true"/>
+	</bean>
+
+	<bean id="singleLogoutProfile" class="org.springframework.security.saml.websso.SingleLogoutProfileImpl"/>
+
+	<bean id="webSSOprofileConsumer" class="cz.muni.ics.oidc.saml.PerunWebSSOProfileConsumerImpl">
+		<property name="enableComparison" value="${saml.acrs.enableComparison}"/>
+		<property name="reservedPrefixes" value="#{'${saml.acrs.reserverdPrefixes}'.split('\s*,\s*')}"/>
+		<property name="maxAuthenticationAge" value="360"/>
+	</bean>
+
+	<bean id="webSSOprofile" class="org.springframework.security.saml.websso.WebSSOProfileImpl"/>
+
+	<bean id="hokWebSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/>
+
+	<bean id="samlUserDetailsService" class="cz.muni.ics.oidc.saml.PerunSamlUserDetailsService"/>
+
+	<bean id="authenticationProvider" class="cz.muni.ics.oidc.saml.PerunSamlAuthenticationProvider">
+		<constructor-arg name="adminIds" value="#{'${admins}'.split('\s*,\s*')}"/>
+	</bean>
+
+	<bean class="org.springframework.security.saml.SAMLBootstrap"/>
+
+	<!-- END SAML -->
+
+	<bean id="accessTokenClaimsModifier" class="${accessTokenClaimsModifier}"/>
+
+	<bean id="tokenEnhancer" class="cz.muni.ics.oidc.server.PerunAccessTokenEnhancer" primary="true">
+		<property name="accessTokenClaimsModifier" ref="accessTokenClaimsModifier"/>
+		<constructor-arg name="configBean" ref="configBean"/>
+		<constructor-arg name="clientService" ref="defaultOAuth2ClientDetailsEntityService"/>
+		<constructor-arg name="jwtService" ref="defaultsignerService"/>
+		<constructor-arg name="connectTokenService" ref="oidcTokenService"/>
+		<constructor-arg name="userInfoService" ref="userInfoService"/>
+	</bean>
+
+	<bean id="oidcTokenService" class="cz.muni.ics.oidc.server.PerunOIDCTokenService" primary="true"/>
+
+	<bean id="callPerunFiltersFilter" class="cz.muni.ics.oidc.server.filters.CallPerunFiltersFilter"/>
+
+	<bean id="localization" class="cz.muni.ics.oidc.web.langs.Localization">
+		<constructor-arg name="perunOidcConfig" ref="perunOidcConfig"/>
+	</bean>
+
+	<bean id="htmlClasses" class="cz.muni.ics.oidc.web.WebHtmlClasses">
+		<constructor-arg name="perunOidcConfig" ref="perunOidcConfig"/>
+	</bean>
+
+	<!-- communicates with Perun -->
+
+	<bean id="perunConnectorRpc" class="cz.muni.ics.oidc.server.connectors.PerunConnectorRpc">
+		<constructor-arg name="perunUrl" value="${perun.rpc.url}"/>
+		<constructor-arg name="perunUser" value="${perun.rpc.user}"/>
+		<constructor-arg name="perunPassword" value="${perun.rpc.password}"/>
+		<constructor-arg name="enabled" value="${perun.rpc.enabled}"/>
+		<constructor-arg name="serializer" value="${perun.rpc.serializer}"/>
+	</bean>
+
+	<bean id="perunAdapterMethodsRpc" class="cz.muni.ics.oidc.server.adapters.impl.PerunAdapterRpc">
+		<property name="connectorRpc" ref="perunConnectorRpc"/>
+		<property name="oidcClientIdAttr" value="clientId" />
+		<property name="oidcCheckMembershipAttr" value="checkGroupMembership"/>
+		<property name="affiliationsAttr" value="${ues.affiliations.attr}"/>
+		<property name="orgUrlAttr" value="${ues.orgUrl.attr}"/>
+	</bean>
+
+	<bean id="perunConnectorLdap" class="cz.muni.ics.oidc.server.connectors.PerunConnectorLdap">
+		<constructor-arg name="ldapHost" value="${ldap.host}"/>
+		<constructor-arg name="ldapUser" value="${ldap.user}"/>
+		<constructor-arg name="ldapPassword" value="${ldap.password}"/>
+		<constructor-arg name="port" value="${ldap.port}"/>
+		<constructor-arg name="useTLS" value="${ldap.starttls}"/>
+		<constructor-arg name="useSSL" value="${ldap.ssl}"/>
+		<constructor-arg name="timeoutSecs" value="${ldap.timeoutSecs}"/>
+		<constructor-arg name="baseDN" value="${ldap.baseDN}"/>
+		<constructor-arg name="allowUntrustedSsl" value="${ldap.allowUntrustedSsl}"/>
+	</bean>
+
+	<bean id="perunAdapterMethodsLdap" class="cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdap">
+		<property name="connectorLdap" ref="perunConnectorLdap"/>
+		<property name="oidcClientIdAttr" value="clientId" />
+		<property name="oidcCheckMembershipAttr" value="checkGroupMembership"/>
+	</bean>
+
+	<bean id="userAttributesMappingService" class="cz.muni.ics.oidc.server.AttributeMappingsService">
+		<constructor-arg name="attrIdentifiersFixed" value="#{'${user.attribute_names.fixedList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrIdentifiersCustom" value="#{'${user.attribute_names.customList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrMappingsProperties" ref="userAttrMappingsProperties" />
+	</bean>
+
+	<bean id="facilityAttributesMappingService" class="cz.muni.ics.oidc.server.AttributeMappingsService">
+		<constructor-arg name="attrIdentifiersFixed" value="#{'${facility.attribute_names.fixedList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrIdentifiersCustom" value="#{'${facility.attribute_names.customList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrMappingsProperties" ref="facilityAttrMappingsProperties" />
+	</bean>
+
+	<bean id="groupAttributesMappingService" class="cz.muni.ics.oidc.server.AttributeMappingsService">
+		<constructor-arg name="attrIdentifiersFixed" value="#{'${group.attribute_names.fixedList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrIdentifiersCustom" value="#{'${group.attribute_names.customList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrMappingsProperties" ref="groupAttrMappingsProperties" />
+	</bean>
+
+	<bean id="voAttributesMappingService" class="cz.muni.ics.oidc.server.AttributeMappingsService">
+		<constructor-arg name="attrIdentifiersFixed" value="#{'${vo.attribute_names.fixedList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrIdentifiersCustom" value="#{'${vo.attribute_names.customList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrMappingsProperties" ref="voAttrMappingsProperties" />
+	</bean>
+
+	<bean id="resourceAttributesMappingService" class="cz.muni.ics.oidc.server.AttributeMappingsService">
+		<constructor-arg name="attrIdentifiersFixed" value="#{'${resource.attribute_names.fixedList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrIdentifiersCustom" value="#{'${resource.attribute_names.customList}'.split('\s*,\s*')}" />
+		<constructor-arg name="attrMappingsProperties" ref="resourceAttrMappingsProperties" />
+	</bean>
+
+	<bean id="perunAdapter" class="cz.muni.ics.oidc.server.adapters.impl.PerunAdapterImpl">
+		<property name="adapterLdap" ref="perunAdapterMethodsLdap"/>
+		<property name="adapterRpc" ref="perunAdapterMethodsRpc"/>
+		<property name="adapterFallback" ref="perunAdapterMethodsRpc"/>
+		<property name="adapterPrimary" ref="#{ '${perun.adapter.primary}' == 'LDAP' ? 'perunAdapterMethodsLdap' : 'perunAdapterMethodsRpc'}"/>
+		<property name="callFallback" value="${perun.adapter.callFallback}"/>
+	</bean>
+
+</beans>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/about.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/about.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/about.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/about.jsp
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/aup.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/aup.jsp
new file mode 100644
index 000000000..c90793e5d
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/aup.jsp
@@ -0,0 +1,39 @@
+<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+
+<%
+
+List<String> cssLinks = new ArrayList<>();
+
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['aup_header']}" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+<h1>${langProps['aup_header']}</h1>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <h3>${langProps['must_agree_aup']}</h3>
+    <form method="POST" action="">
+        <c:forEach var="aup" items="${newAups}">
+            <div>
+                <p style="font-size: 16px; padding: 0; margin: 0;">${langProps['org_vo']} ${" "}<strong><c:out value="${aup.key}"/></strong></p>
+                <p>${langProps['see_aup']}${" "}${aup.value.version}${" "}<a href="<c:out value="${aup.value.link}"/>">${langProps['here']}</a></p>
+            </div>
+        </c:forEach>
+        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
+        <div class="form-group">
+            <input type="submit" value="${langProps['agree_aup']}" class="btn btn-lg btn-primary btn-block">
+        </div>
+    </form>
+</div>
+</div><!-- wrap -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/contact.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/contact.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/contact.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/contact.jsp
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/continue_direct.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/continue_direct.jsp
new file mode 100644
index 000000000..1dd7fec9d
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/continue_direct.jsp
@@ -0,0 +1,34 @@
+<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+
+<%
+
+List<String> cssLinks = new ArrayList<>();
+
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['continue_direct_title']}" reqURL="${reqURL}" baseURL="${baseURL}"
+          cssLinks="${cssLinks}" theme="${theme}"/>
+
+<h1>${langProps['continue_direct_header']}</h1>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div id="head">
+        <h1>${langProps['continue_direct_heading']}</h1>
+    </div>
+    <p>${langProps['continue_direct_text']}</p>
+    <hr/>
+    <br/>
+    <a href="${fn:escapeXml(target)} "class="btn btn-lg btn-primary btn-block">${langProps['continue_direct_btn']}</a>
+</div>
+</div><!-- wrap -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/deviceApproved.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/deviceApproved.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/deviceApproved.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/deviceApproved.jsp
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/error.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/error.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/error.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/error.jsp
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/home.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/home.jsp
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/isTestSpWarning.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/isTestSpWarning.jsp
new file mode 100644
index 000000000..3322c47a7
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/isTestSpWarning.jsp
@@ -0,0 +1,39 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+
+<%
+    List<String> cssLinks = new ArrayList<>();
+    pageContext.setAttribute("cssLinks", cssLinks);
+%>
+
+<t:header title="${langProps['is_test_sp_warning_title']}" reqURL="${reqURL}"
+          baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div id="head">
+        <h1>${langProps['is_test_sp_warning_header']}</h1>
+    </div>
+    <p>${langProps['is_test_sp_warning_text']}</p>
+
+    <form method="GET" action="${action}">
+        <hr/>
+        <br/>
+        <input type="hidden" name="target" value="${fn:escapeXml(target)}">
+        <input type="hidden" name="accepted" value="true">
+        <input type="submit" name="continue" value="${langProps['is_test_sp_warning_continue']}"
+               class="btn btn-lg btn-primary btn-block">
+    </form>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
\ No newline at end of file
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/login.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/login.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/login.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/login.jsp
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/login_failure.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/login_failure.jsp
new file mode 100644
index 000000000..035ae11ae
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/login_failure.jsp
@@ -0,0 +1,31 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+<%
+
+List<String> cssLinks = new ArrayList<>();
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['login_failure_title']}" reqURL="${reqURL}" baseURL="${baseURL}"
+          cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div id="head">
+        <h1>${fn:escapeXml(langProps['login_failure_header'])}</h1>
+    </div>
+    <div class="msg">${langProps['login_failure_msg']}</div>
+    <div class="msg">${langProps['login_failure_contact_us']}<a href="mailto:${contactMail}">${contactMail}</a>.</div>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/login_success.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/login_success.jsp
new file mode 100644
index 000000000..c6da0c4a0
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/login_success.jsp
@@ -0,0 +1,30 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+<%
+
+List<String> cssLinks = new ArrayList<>();
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['login_success_title']}" reqURL="${reqURL}" baseURL="${baseURL}"
+          cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div id="head">
+        <h1>${fn:escapeXml(langProps['login_success_header'])}</h1>
+    </div>
+    <div class="msg">${langProps['login_success_msg']}</div>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout.jsp
new file mode 100644
index 000000000..0d678f6d1
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout.jsp
@@ -0,0 +1,42 @@
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+
+<%
+
+	List<String> cssLinks = new ArrayList<>();
+	pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="Logout" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+<h1>${langProps['logout.confirmation.header']}</h1>
+</div> <%-- header --%>
+
+<div id="content">
+    <form action="${config.issuer}${config.issuer.endsWith('/') ? '' : '/'}endsession" method="POST">
+        <p>${langProps["logout.confirmation.explanation"]}</p>
+        <div class="row">
+            <div class="col-md-6 mb-4">
+                <input name="approve" value="${langProps["logout.confirmation.submit"]}"
+                       type="submit" class="btn btn-lg btn-block btn-primary" />
+            </div>
+            <div class="col-md-6 mb-4">
+                <input name="deny" value="${langProps["logout.confirmation.deny"]}"
+                       type="submit" class="btn btn-lg btn-block" />
+            </div>
+        </div>
+        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
+    </form>
+</div>
+
+</div><!-- wrap -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout_denied.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout_denied.jsp
new file mode 100644
index 000000000..f9886bd86
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout_denied.jsp
@@ -0,0 +1,30 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+<%
+
+List<String> cssLinks = new ArrayList<>();
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['logout_denied_title']}" reqURL="${reqURL}" baseURL="${baseURL}"
+          cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div id="head">
+        <h1>${fn:escapeXml(langProps['logout_denied_header'])}</h1>
+    </div>
+    <div class="msg">${langProps['logout_denied_msg']}</div>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout_success.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout_success.jsp
new file mode 100644
index 000000000..aaed2b8aa
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/logout_success.jsp
@@ -0,0 +1,30 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+<%
+
+List<String> cssLinks = new ArrayList<>();
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['logout_success_title']}" reqURL="${reqURL}" baseURL="${baseURL}"
+          cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div id="head">
+        <h1>${fn:escapeXml(langProps['logout_success_header'])}</h1>
+    </div>
+    <div class="msg">${langProps['logout_success_msg']}</div>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/manage.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/manage.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/manage.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/manage.jsp
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/registrationForm.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/registrationForm.jsp
new file mode 100644
index 000000000..fb8af4cd3
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/registrationForm.jsp
@@ -0,0 +1,71 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+<%
+
+String samlCssUrl = (String) pageContext.getAttribute("samlResourcesURL");
+List<String> cssLinks = new ArrayList<>();
+
+cssLinks.add(samlCssUrl + "/module.php/perun/res/css/perun_identity_choose_vo_and_group.css");
+
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['registration_title']}" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div id="head">
+        <h1>${fn:escapeXml(langProps['registration_header1'])}
+            <c:choose>
+                <c:when test="${not empty client.clientName and not empty client.clientUri}">
+                    &#32;<a href="${fn:escapeXml(client.clientUri)}">${fn:escapeXml(client.clientName)}</a>
+                </c:when>
+                <c:when test="${not empty client.clientName}">
+                    &#32;${fn:escapeXml(client.clientName)}
+                </c:when>
+            </c:choose>
+            &#32;${langProps['registration_header2']}
+        </h1>
+    </div>
+    <div class="msg">${langProps['registration_message']}</div>
+
+    <div class="list-group">
+        <form action="${action}" method="get">
+            <h4>${langProps['registration_select_vo']}</h4>
+            <select id="selectVo" class="form-control" name="selectedVo" onchange="filter()" required>
+                <c:forEach var="voGroupPair" items="${groupsForRegistration}">
+                    <option value="${fn:escapeXml(voGroupPair.key.shortName)}">
+                            ${fn:escapeXml(voGroupPair.key.name)}
+                    </option>
+                </c:forEach>
+            </select>
+
+            <h4 class="selectGroup" style="display: none">${langProps['registration_select_group']}</h4>
+            <select  class="selectGroup form-control" name="selectedGroup" class="form-control" style="display: none" required>
+                <c:forEach var="voGroupPair" items="${groupsForRegistration}">
+                    <c:forEach var="group" items="${voGroupPair.value}">
+                        <option class="groupOption" value="${fn:escapeXml(voGroupPair.key.shortName)}:${fn:escapeXml(group.name)}">
+                                ${fn:escapeXml(group.description)}
+                        </option>
+                    </c:forEach>
+                </c:forEach>
+            </select>
+
+            <input type="submit" value="${langProps['registration_continue']}" class="btn btn-lg btn-primary btn-block">
+        </form>
+    </div>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
+
+<script type="text/javascript" src="resources/js/reg_form_select.js"></script>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/registrationFormContinue.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/registrationFormContinue.jsp
new file mode 100644
index 000000000..a8bf430b9
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/registrationFormContinue.jsp
@@ -0,0 +1,51 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+<%
+
+String samlCssUrl = (String) pageContext.getAttribute("samlResourcesURL");
+List<String> cssLinks = new ArrayList<>();
+
+cssLinks.add(samlCssUrl + "/module.php/perun/res/css/perun_identity_go_to_registration.css");
+
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['go_to_registration_title']}" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div id="head">
+        <h1>${langProps['go_to_registration_header1']}
+            <c:choose>
+                <c:when test="${not empty client.clientName and not empty client.clientUri}">
+                    &#32;<a href="${fn:escapeXml(client.uri)}">${fn:escapeXml(client.clientName)}</a>
+                </c:when>
+                <c:when test="${not empty client.clientName}">
+                    &#32;${fn:escapeXml(client.clientName)}
+                </c:when>
+            </c:choose>
+            &#32;${langProps['go_to_registration_header2']}
+        </h1>
+    </div>
+    <form method="GET" action="${action}">
+        <hr/>
+        <br/>
+        <input type="hidden" name="client_id" value="${fn:escapeXml(client_id)}" />
+        <input type="hidden" name="facility_id" value="${fn:escapeXml(facility_id)}" />
+        <input type="hidden" name="user_id" value="${fn:escapeXml(user_id)}" />
+        <input type="submit" name="continueToRegistration" value="${langProps['go_to_registration_continue']}"
+               class="btn btn-lg btn-primary btn-block">
+    </form>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
\ No newline at end of file
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApprove.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApprove.jsp
new file mode 100644
index 000000000..f65506dca
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApprove.jsp
@@ -0,0 +1,54 @@
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+<%
+
+	String samlCssUrl = (String) pageContext.getAttribute("samlResourcesURL");
+	List<String> cssLinks = new ArrayList<>();
+
+	cssLinks.add(samlCssUrl + "/module.php/consent/assets/css/consent.css");
+	cssLinks.add(samlCssUrl + "/module.php/perun/res/css/consent.css");
+
+	pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['consent_title']}" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+<h1 class="h3">${langProps['consent_header']} ${" "} ${fn:escapeXml(client.clientName)}</h1>
+
+</div> <%-- header --%>
+
+<div id="content">
+	<c:remove scope="session" var="SPRING_SECURITY_LAST_EXCEPTION" />
+	<form name="confirmationForm"
+		  action="${pageContext.request.contextPath.endsWith('/') ? pageContext.request.contextPath : pageContext.request.contextPath.concat('/')}authorize" method="post">
+		<p>
+			<c:if test="${not empty client.policyUri}">
+				${langProps['consent_privacy_policy']}
+				&#32;<a target='_blank' href='${fn:escapeXml(client.policyUri)}'><em>${fn:escapeXml(client.clientName)}</em></a>
+			</c:if>
+		</p>
+		<t:attributesConsent />
+		<div class="row" id="saveconsentcontainer">
+			<div class="col-xs-12">
+				<div class="checkbox">
+					<input type="checkbox" name="remember" id="saveconsent" value="until-revoked"/>
+					<label for="saveconsent">${langProps['remember']}</label>
+				</div>
+			</div>
+		</div>
+		<input id="user_oauth_approval" name="user_oauth_approval" value="true" type="hidden" />
+		<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
+		<t:consentButtons />
+	</form>
+</div>
+</div><!-- wrap -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApproveDevice.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApproveDevice.jsp
new file mode 100644
index 000000000..9c78e5cc9
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedApproveDevice.jsp
@@ -0,0 +1,50 @@
+<%@ page import="cz.muni.ics.oidc.server.elixir.GA4GHClaimSource" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+
+<%
+
+	String samlCssUrl = (String) pageContext.getAttribute("samlResourcesURL");
+	List<String> cssLinks = new ArrayList<>();
+
+	cssLinks.add(samlCssUrl + "/module.php/consent/assets/css/consent.css");
+	cssLinks.add(samlCssUrl + "/module.php/perun/res/css/consent.css");
+
+	pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+    <t:header title="${langProps['device_approve_title']}" reqURL="${reqURL}" baseURL="${baseURL}"
+              cssLinks="${cssLinks}" theme="${theme}"/>
+
+    <h1 class="h3">${langProps['device_approve_header']} ${" "} ${fn:escapeXml(client.clientName)}</h1>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <c:remove scope="session" var="SPRING_SECURITY_LAST_EXCEPTION" />
+        <form name="confirmationForm"
+              action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/approve" method="post">
+            <p>
+                <c:if test="${not empty client.policyUri}">
+                    ${langProps['device_approve_privacy']}
+                    &#32;<a target='_blank' href='${fn:escapeXml(client.policyUri)}'><em>${fn:escapeXml(client.clientName)}</em></a>
+                </c:if>
+            </p>
+            <t:attributesConsent/>
+            <input id="user_oauth_approval" name="user_oauth_approval" value="true" type="hidden" />
+            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
+            <input type="hidden" name="user_code" value="${ dc.userCode }" />
+            <t:consentButtons/>
+        </form>
+    </div>
+</div><!-- wrap -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedDeviceApproved.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedDeviceApproved.jsp
new file mode 100644
index 000000000..111be0a7d
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedDeviceApproved.jsp
@@ -0,0 +1,45 @@
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+
+<%
+
+List<String> cssLinks = new ArrayList<>();
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${langProps['device_approved_title']}" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content" class="text-center">
+    <h1>
+        <c:if test="${ approved }"><p>&#x2714; ${langProps['device_approved_approved']}</p></c:if>
+        <c:if test="${ not approved }"><p>&#x2717; ${langProps['device_approved_rejected']}</p></c:if>
+    </h1>
+    <p class="mt-2">
+        <c:if test="${ approved }">
+            ${langProps['device_approved_text_approved_start']}${" "}
+            <c:if test="${empty client.clientName}"><em><c:out value="${client.clientId}" /></em></c:if>
+            <c:if test="${not empty client.clientName}"><em><c:out value="${client.clientName}" /></em></c:if>
+            ${" "}${langProps['device_approved_text_approved_end']}
+        </c:if>
+        <c:if test="${not approved}">
+            ${langProps['device_approved_text_rejected_start']}${" "}
+            <c:if test="${empty client.clientName}"><em><c:out value="${client.clientId}" /></em></c:if>
+            <c:if test="${not empty client.clientName}"><em><c:out value="${client.clientName}" /></em></c:if>
+            ${". "}${langProps['device_approved_text_rejected_end']}
+        </c:if>
+    </p>
+</div>
+
+</div> <%-- wrap --%>
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedRequestUserCode.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedRequestUserCode.jsp
new file mode 100644
index 000000000..bc60de450
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/themedRequestUserCode.jsp
@@ -0,0 +1,69 @@
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+
+<c:set var="baseURL" value="${baseURL}"/>
+<c:set var="samlResourcesURL" value="${samlResourcesURL}"/>
+
+<%
+
+    List<String> cssLinks = new ArrayList<>();
+    pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+    <t:header title="${langProps['request_code_title']}" reqURL="${reqURL}" baseURL="${baseURL}"
+              cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content" class="text-center">
+    <h1>${langProps['request_code_header']}</h1>
+    <c:choose>
+        <c:when test="${ not empty error }">
+            <p class="alert alert-danger mt-2">
+            <c:choose>
+                <c:when test="${ error == 'noUserCode' }">${langProps['user_code_empty_or_not_found']}</c:when>
+                <c:when test="${ error == 'expiredUserCode' }">${langProps['user_code_expired']}</c:when>
+                <c:when test="${ error == 'userCodeAlreadyApproved' }">${langProps['user_code_already_approved']}</c:when>
+                <c:when test="${ error == 'userCodeMismatch' }">${langProps['user_code_mismatch']}</c:when>
+                <c:otherwise>${langProps['user_code_error']}</c:otherwise>
+            </c:choose>
+            </p>
+        </c:when>
+        <c:otherwise>
+            <p class="mt-2">
+                ${langProps['user_code_info']}
+            </p>
+        </c:otherwise>
+    </c:choose>
+
+    <form name="confirmationForm" class="mt-2"
+          action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }device/verify" method="post">
+        <div class="row-fluid">
+            <div class="span12">
+                <div>
+                    <div class="input-block-level input-xlarge">
+                            <input type="text" name="user_code" placeholder="${langProps['code']}"
+                                   autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false"
+                                   value="" />
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="row-fluid mt-2">
+            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
+            <input type="hidden" name="acr" value="${acr}">
+            <input name="approve" value="${langProps['user_code_submit']}" type="submit"
+                   class="btn btn-success btn-block btn-large" />
+        </div>
+
+    </form>
+</div>
+
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
\ No newline at end of file
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved.jsp
new file mode 100644
index 000000000..49ffd8a08
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved.jsp
@@ -0,0 +1,48 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+<%
+
+List<String> cssLinks = new ArrayList<>();
+
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${title}" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div class="error_message" style="word-wrap: break-word;">
+        <c:forEach var="contactIter" items="${client.contacts}" end="0">
+            <c:set var="contact" value="${contactIter}" />
+        </c:forEach>
+        <c:if test="${empty contact}">
+            <c:set var="contact" value="${contactMail}"/>
+        </c:if>
+        <h1>${langProps['403_header']}</h1>
+        <p>${langProps['403_text']}&#32;${fn:escapeXml(client.clientName)}
+            <c:if test="${not empty client.clientUri}">
+                <br/>
+                ${langProps['403_informationPage']}&#32;
+                <a href="${fn:escapeXml(client.clientUri)}">
+                    ${fn:escapeXml(client.clientUri)}
+                </a>
+            </c:if>
+        </p>
+
+        <p>${langProps['403_contactSupport']}&#32;
+           <a href="mailto:${contact}?subject=${langProps["403_subject"]} ${fn:escapeXml(client.clientName)}">
+               ${fn:escapeXml(contact)}
+           </a>
+        </p>
+    </div>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved_spec.jsp b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved_spec.jsp
new file mode 100644
index 000000000..6a2535752
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/views/unapproved_spec.jsp
@@ -0,0 +1,29 @@
+<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
+<%@ page import="java.util.ArrayList" %>
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="t" tagdir="/WEB-INF/tags/common"%>
+
+<%
+
+List<String> cssLinks = new ArrayList<>();
+
+pageContext.setAttribute("cssLinks", cssLinks);
+
+%>
+
+<t:header title="${title}" reqURL="${reqURL}" baseURL="${baseURL}" cssLinks="${cssLinks}" theme="${theme}"/>
+
+</div> <%-- header --%>
+
+<div id="content">
+    <div class="error_message" style="word-wrap: break-word;">
+        <h1><c:out value="${outHeader}" escapeXml="false"/></h1>
+        <p><c:out value="${outMessage}" escapeXml="false"/></p>
+        <p>${langProps['contact_p']}${" "}<a href="mailto:${contactMail}">${contactMail}</a></p>
+    </div>
+</div>
+</div><!-- ENDWRAP -->
+
+<t:footer baseURL="${baseURL}" theme="${theme}"/>
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/web.xml b/perun-oidc-server-webapp/src/main/webapp/WEB-INF/web.xml
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/WEB-INF/web.xml
rename to perun-oidc-server-webapp/src/main/webapp/WEB-INF/web.xml
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap/img/glyphicons-halflings-white.png b/perun-oidc-server-webapp/src/main/webapp/resources/bootstrap/img/glyphicons-halflings-white.png
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/bootstrap/img/glyphicons-halflings-white.png
rename to perun-oidc-server-webapp/src/main/webapp/resources/bootstrap/img/glyphicons-halflings-white.png
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap/img/glyphicons-halflings.png b/perun-oidc-server-webapp/src/main/webapp/resources/bootstrap/img/glyphicons-halflings.png
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/bootstrap/img/glyphicons-halflings.png
rename to perun-oidc-server-webapp/src/main/webapp/resources/bootstrap/img/glyphicons-halflings.png
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap/js/bootstrap.js b/perun-oidc-server-webapp/src/main/webapp/resources/bootstrap/js/bootstrap.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/bootstrap/js/bootstrap.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/bootstrap/js/bootstrap.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap/js/bootstrap.min.js b/perun-oidc-server-webapp/src/main/webapp/resources/bootstrap/js/bootstrap.min.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/bootstrap/js/bootstrap.min.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/bootstrap/js/bootstrap.min.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/css/bootstrap-responsive.min.css b/perun-oidc-server-webapp/src/main/webapp/resources/css/bootstrap-responsive.min.css
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/css/bootstrap-responsive.min.css
rename to perun-oidc-server-webapp/src/main/webapp/resources/css/bootstrap-responsive.min.css
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/css/bootstrap-sheet.css b/perun-oidc-server-webapp/src/main/webapp/resources/css/bootstrap-sheet.css
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/css/bootstrap-sheet.css
rename to perun-oidc-server-webapp/src/main/webapp/resources/css/bootstrap-sheet.css
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/css/bootstrap.min.css b/perun-oidc-server-webapp/src/main/webapp/resources/css/bootstrap.min.css
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/css/bootstrap.min.css
rename to perun-oidc-server-webapp/src/main/webapp/resources/css/bootstrap.min.css
diff --git a/perun-oidc-server-webapp/src/main/webapp/resources/css/customs.css b/perun-oidc-server-webapp/src/main/webapp/resources/css/customs.css
new file mode 100644
index 000000000..69684b74b
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/resources/css/customs.css
@@ -0,0 +1,36 @@
+.mt-0 {
+    margin-top: 0 !important;
+}
+.mr-half {
+    margin-right: .5em !important;
+}
+.checkbox-wrapper {
+    float: left;
+}
+.h4 {
+    clear: inherit !important;
+}
+.h1, .h2, .h3, .h4, .h5, .h6 {
+    letter-spacing: normal;
+}
+.mb-0 {
+    margin-bottom: 0 !important;
+}
+.mb-4 {
+    margin-bottom: 1.5rem;
+}
+.mt-0 {
+    margin-top: 0 !important;
+}
+.oh {
+    overflow: hidden;
+}
+.mt-2 {
+    margin-top: 2% !important;
+}
+.cw {
+    color: #FAFAFA !important;
+}
+.hidden {
+    display: none;
+}
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/css/mitreid-connect-local.css b/perun-oidc-server-webapp/src/main/webapp/resources/css/mitreid-connect-local.css
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/css/mitreid-connect-local.css
rename to perun-oidc-server-webapp/src/main/webapp/resources/css/mitreid-connect-local.css
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/css/mitreid-connect-responsive-local.css b/perun-oidc-server-webapp/src/main/webapp/resources/css/mitreid-connect-responsive-local.css
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/css/mitreid-connect-responsive-local.css
rename to perun-oidc-server-webapp/src/main/webapp/resources/css/mitreid-connect-responsive-local.css
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/css/mitreid-connect-responsive.css b/perun-oidc-server-webapp/src/main/webapp/resources/css/mitreid-connect-responsive.css
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/css/mitreid-connect-responsive.css
rename to perun-oidc-server-webapp/src/main/webapp/resources/css/mitreid-connect-responsive.css
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/css/mitreid-connect.css b/perun-oidc-server-webapp/src/main/webapp/resources/css/mitreid-connect.css
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/css/mitreid-connect.css
rename to perun-oidc-server-webapp/src/main/webapp/resources/css/mitreid-connect.css
diff --git a/perun-oidc-server-webapp/src/main/webapp/resources/images/arrow.png b/perun-oidc-server-webapp/src/main/webapp/resources/images/arrow.png
new file mode 100644
index 000000000..ce608b467
Binary files /dev/null and b/perun-oidc-server-webapp/src/main/webapp/resources/images/arrow.png differ
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/images/heart_mode.png b/perun-oidc-server-webapp/src/main/webapp/resources/images/heart_mode.png
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/images/heart_mode.png
rename to perun-oidc-server-webapp/src/main/webapp/resources/images/heart_mode.png
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/images/logo_placeholder.gif b/perun-oidc-server-webapp/src/main/webapp/resources/images/logo_placeholder.gif
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/images/logo_placeholder.gif
rename to perun-oidc-server-webapp/src/main/webapp/resources/images/logo_placeholder.gif
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/images/mitreid-connect.ico b/perun-oidc-server-webapp/src/main/webapp/resources/images/mitreid-connect.ico
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/images/mitreid-connect.ico
rename to perun-oidc-server-webapp/src/main/webapp/resources/images/mitreid-connect.ico
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/images/openid_connect_large.png b/perun-oidc-server-webapp/src/main/webapp/resources/images/openid_connect_large.png
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/images/openid_connect_large.png
rename to perun-oidc-server-webapp/src/main/webapp/resources/images/openid_connect_large.png
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/images/openid_connect_small.png b/perun-oidc-server-webapp/src/main/webapp/resources/images/openid_connect_small.png
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/images/openid_connect_small.png
rename to perun-oidc-server-webapp/src/main/webapp/resources/images/openid_connect_small.png
diff --git a/perun-oidc-server-webapp/src/main/webapp/resources/images/perun_24px.png b/perun-oidc-server-webapp/src/main/webapp/resources/images/perun_24px.png
new file mode 100644
index 000000000..c0134b936
Binary files /dev/null and b/perun-oidc-server-webapp/src/main/webapp/resources/images/perun_24px.png differ
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/admin.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/admin.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/admin.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/admin.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/blacklist.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/blacklist.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/blacklist.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/blacklist.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/client.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/client.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/client.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/client.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/dynreg.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/dynreg.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/dynreg.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/dynreg.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/grant.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/grant.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/grant.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/grant.js
diff --git a/perun-oidc-server-webapp/src/main/webapp/resources/js/jquery-3-3-1.min.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/jquery-3-3-1.min.js
new file mode 100644
index 000000000..4d9b3a258
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/resources/js/jquery-3-3-1.min.js
@@ -0,0 +1,2 @@
+/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */
+!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:n.sort,splice:n.splice},w.extend=w.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||g(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)n=a[t],a!==(r=e[t])&&(l&&r&&(w.isPlainObject(r)||(i=Array.isArray(r)))?(i?(i=!1,o=n&&Array.isArray(n)?n:[]):o=n&&w.isPlainObject(n)?n:{},a[t]=w.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},w.extend({expando:"jQuery"+("3.3.1"+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==c.call(e))&&(!(t=i(e))||"function"==typeof(n=f.call(t,"constructor")&&t.constructor)&&p.call(n)===d)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e){m(e)},each:function(e,t){var n,r=0;if(C(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(C(Object(e))?w.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:u.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r,i=[],o=0,a=e.length,s=!n;o<a;o++)(r=!t(e[o],o))!==s&&i.push(e[o]);return i},map:function(e,t,n){var r,i,o=0,s=[];if(C(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&s.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&s.push(i);return a.apply([],s)},guid:1,support:h}),"function"==typeof Symbol&&(w.fn[Symbol.iterator]=n[Symbol.iterator]),w.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function C(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!g(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",I="\\["+M+"*("+R+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+R+"))|)"+M+"*\\]",W=":("+R+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+I+")*)|.*)\\)|)",$=new RegExp(M+"+","g"),B=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),F=new RegExp("^"+M+"*,"+M+"*"),_=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="<a id='"+b+"'></a><select id='"+b+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:he(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:he(function(e,t,n){for(var r=n<0?n+t:n;--r>=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=r.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})r.pseudos[t]=fe(t);for(t in{submit:!0,reset:!0})r.pseudos[t]=pe(t);function ye(){}ye.prototype=r.filters=r.pseudos,r.setFilters=new ye,a=oe.tokenize=function(e,t){var n,i,o,a,s,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=r.preFilter;while(s){n&&!(i=F.exec(s))||(i&&(s=s.slice(i[0].length)||s),u.push(o=[])),n=!1,(i=_.exec(s))&&(n=i.shift(),o.push({value:n,type:i[0].replace(B," ")}),s=s.slice(n.length));for(a in r.filter)!(i=V[a].exec(s))||l[a]&&!(i=l[a](i))||(n=i.shift(),o.push({value:n,type:a,matches:i}),s=s.slice(n.length));if(!n)break}return t?s.length:s?oe.error(e):k(e,u).slice(0)};function ve(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function me(e,t,n){var r=t.dir,i=t.next,o=i||r,a=n&&"parentNode"===o,s=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||a)return e(t,n,i);return!1}:function(t,n,u){var l,c,f,p=[T,s];if(u){while(t=t[r])if((1===t.nodeType||a)&&e(t,n,u))return!0}else while(t=t[r])if(1===t.nodeType||a)if(f=t[b]||(t[b]={}),c=f[t.uniqueID]||(f[t.uniqueID]={}),i&&i===t.nodeName.toLowerCase())t=t[r]||t;else{if((l=c[o])&&l[0]===T&&l[1]===s)return p[2]=l[2];if(c[o]=p,p[2]=e(t,n,u))return!0}return!1}}function xe(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r<i;r++)oe(e,t[r],n);return n}function we(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Te(e,t,n,r,i,o){return r&&!r[b]&&(r=Te(r)),i&&!i[b]&&(i=Te(i,o)),se(function(o,a,s,u){var l,c,f,p=[],d=[],h=a.length,g=o||be(t||"*",s.nodeType?[s]:s,[]),y=!e||!o&&t?g:we(g,p,e,s,u),v=n?i||(o?e:h||r)?[]:a:y;if(n&&n(y,v,s,u),r){l=we(v,d),r(l,[],s,u),c=l.length;while(c--)(f=l[c])&&(v[d[c]]=!(y[d[c]]=f))}if(o){if(i||e){if(i){l=[],c=v.length;while(c--)(f=v[c])&&l.push(y[c]=f);i(null,v=[],l,u)}c=v.length;while(c--)(f=v[c])&&(l=i?O(o,f):p[c])>-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u<o;u++)if(n=r.relative[e[u].type])p=[me(xe(p),n)];else{if((n=r.filter[e[u].type].apply(null,e[u].matches))[b]){for(i=++u;i<o;i++)if(r.relative[e[i].type])break;return Te(u>1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u<i&&Ce(e.slice(u,i)),i<o&&Ce(e=e.slice(i)),i<o&&ve(e))}p.push(n)}return xe(p)}function Ee(e,t){var n=t.length>0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t<r;t++)if(w.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)w.find(e,i[t],n);return r>1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(w.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&w(e);if(!D.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?a.index(n)>-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s<o.length)!1===o[s].apply(n[0],n[1])&&e.stopOnFalse&&(s=o.length,n=!1)}e.memory||(n=!1),t=!1,i&&(o=n?[]:"")},l={add:function(){return o&&(n&&!t&&(s=o.length-1,a.push(n)),function t(n){w.each(n,function(n,r){g(r)?e.unique&&l.has(r)||o.push(r):r&&r.length&&"string"!==x(r)&&t(r)})}(arguments),n&&!t&&u()),this},remove:function(){return w.each(arguments,function(e,t){var n;while((n=w.inArray(t,o,n))>-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t<o)){if((e=r.apply(s,u))===n.promise())throw new TypeError("Thenable self-resolution");l=e&&("object"==typeof e||"function"==typeof e)&&e.then,g(l)?i?l.call(e,a(o,n,I,i),a(o,n,W,i)):(o++,l.call(e,a(o,n,I,i),a(o,n,W,i),a(o,n,I,n.notifyWith))):(r!==I&&(s=void 0,u=[e]),(i||n.resolveWith)(s,u))}},c=i?l:function(){try{l()}catch(e){w.Deferred.exceptionHook&&w.Deferred.exceptionHook(e,c.stackTrace),t+1>=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},X=/^-ms-/,U=/-([a-z])/g;function V(e,t){return t.toUpperCase()}function G(e){return e.replace(X,"ms-").replace(U,V)}var Y=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function Q(){this.expando=w.expando+Q.uid++}Q.uid=1,Q.prototype={cache:function(e){var t=e[this.expando];return t||(t={},Y(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[G(t)]=n;else for(r in t)i[G(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][G(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(G):(t=G(t))in r?[t]:t.match(M)||[]).length;while(n--)delete r[t[n]]}(void 0===t||w.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!w.isEmptyObject(t)}};var J=new Q,K=new Q,Z=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,ee=/[A-Z]/g;function te(e){return"true"===e||"false"!==e&&("null"===e?null:e===+e+""?+e:Z.test(e)?JSON.parse(e):e)}function ne(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(ee,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n=te(n)}catch(e){}K.set(e,t,n)}else n=void 0;return n}w.extend({hasData:function(e){return K.hasData(e)||J.hasData(e)},data:function(e,t,n){return K.access(e,t,n)},removeData:function(e,t){K.remove(e,t)},_data:function(e,t,n){return J.access(e,t,n)},_removeData:function(e,t){J.remove(e,t)}}),w.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=K.get(o),1===o.nodeType&&!J.get(o,"hasDataAttrs"))){n=a.length;while(n--)a[n]&&0===(r=a[n].name).indexOf("data-")&&(r=G(r.slice(5)),ne(o,r,i[r]));J.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof e?this.each(function(){K.set(this,e)}):z(this,function(t){var n;if(o&&void 0===t){if(void 0!==(n=K.get(o,e)))return n;if(void 0!==(n=ne(o,e)))return n}else this.each(function(){K.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length<n?w.queue(this[0],e):void 0===t?this:this.each(function(){var n=w.queue(this,e,t);w._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&w.dequeue(this,e)})},dequeue:function(e){return this.each(function(){w.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=w.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=J.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var re=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ie=new RegExp("^(?:([+-])=|)("+re+")([a-z%]*)$","i"),oe=["Top","Right","Bottom","Left"],ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&w.contains(e.ownerDocument,e)&&"none"===w.css(e,"display")},se=function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i};function ue(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return w.css(e,t,"")},u=s(),l=n&&n[3]||(w.cssNumber[t]?"":"px"),c=(w.cssNumber[t]||"px"!==l&&+u)&&ie.exec(w.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)w.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,w.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var le={};function ce(e){var t,n=e.ownerDocument,r=e.nodeName,i=le[r];return i||(t=n.body.appendChild(n.createElement(r)),i=w.css(t,"display"),t.parentNode.removeChild(t),"none"===i&&(i="block"),le[r]=i,i)}function fe(e,t){for(var n,r,i=[],o=0,a=e.length;o<a;o++)(r=e[o]).style&&(n=r.style.display,t?("none"===n&&(i[o]=J.get(r,"display")||null,i[o]||(r.style.display="")),""===r.style.display&&ae(r)&&(i[o]=ce(r))):"none"!==n&&(i[o]="none",J.set(r,"display",n)));for(o=0;o<a;o++)null!=i[o]&&(e[o].style.display=i[o]);return e}w.fn.extend({show:function(){return fe(this,!0)},hide:function(){return fe(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?w(this).show():w(this).hide()})}});var pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n<r;n++)J.set(e[n],"globalEval",!t||J.get(t[n],"globalEval"))}var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===x(o))w.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+w.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;w.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&w.inArray(o,r)>-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="<textarea>x</textarea>",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n<arguments.length;n++)u[n]=arguments[n];if(t.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,t)){s=w.event.handlers.call(this,t,l),n=0;while((o=s[n++])&&!t.isPropagationStopped()){t.currentTarget=o.elem,r=0;while((a=o.handlers[r++])&&!t.isImmediatePropagationStopped())t.rnamespace&&!t.rnamespace.test(a.namespace)||(t.handleObj=a,t.data=a.data,void 0!==(i=((w.event.special[a.origType]||{}).handle||a.handler).apply(o.elem,u))&&!1===(t.result=i)&&(t.preventDefault(),t.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,t),t.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&e.button>=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?w(i,this).index(l)>-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(e,t){Object.defineProperty(w.Event.prototype,e,{enumerable:!0,configurable:!0,get:g(t)?function(){if(this.originalEvent)return t(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[e]},set:function(t){Object.defineProperty(this,e,{enumerable:!0,configurable:!0,writable:!0,value:t})}})},fix:function(e){return e[w.expando]?e:new w.Event(e)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==Se()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===Se()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&N(this,"input"))return this.click(),!1},_default:function(e){return N(e.target,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},w.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},w.Event=function(e,t){if(!(this instanceof w.Event))return new w.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ee:ke,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&w.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[w.expando]=!0},w.Event.prototype={constructor:w.Event,isDefaultPrevented:ke,isPropagationStopped:ke,isImmediatePropagationStopped:ke,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ee,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ee,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ee,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},w.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&we.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&Te.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},w.event.addProp),w.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,t){w.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return i&&(i===r||w.contains(r,i))||(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),w.fn.extend({on:function(e,t,n,r){return De(this,e,t,n,r)},one:function(e,t,n,r){return De(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,w(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=ke),this.each(function(){w.event.remove(this,e,n,t)})}});var Ne=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/<script|<style|<link/i,je=/checked\s*(?:[^=]|=\s*.checked.)/i,qe=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n<r;n++)w.event.add(t,i,l[i][n])}K.hasData(e)&&(s=K.access(e),u=w.extend({},s),K.set(t,u))}}function Me(e,t){var n=t.nodeName.toLowerCase();"input"===n&&pe.test(e.type)?t.checked=e.checked:"input"!==n&&"textarea"!==n||(t.defaultValue=e.defaultValue)}function Re(e,t,n,r){t=a.apply([],t);var i,o,s,u,l,c,f=0,p=e.length,d=p-1,y=t[0],v=g(y);if(v||p>1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f<p;f++)l=i,f!==d&&(l=w.clone(l,!0,!0),u&&w.merge(s,ye(l,"script"))),n.call(e[f],l,f);if(u)for(c=s[s.length-1].ownerDocument,w.map(s,Oe),f=0;f<u;f++)l=s[f],he.test(l.type||"")&&!J.access(l,"globalEval")&&w.contains(c,l)&&(l.src&&"module"!==(l.type||"").toLowerCase()?w._evalUrl&&w._evalUrl(l.src):m(l.textContent.replace(qe,""),c,l))}return e}function Ie(e,t,n){for(var r,i=t?w.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||w.cleanData(ye(r)),r.parentNode&&(n&&w.contains(r.ownerDocument,r)&&ve(ye(r,"script")),r.parentNode.removeChild(r));return e}w.extend({htmlPrefilter:function(e){return e.replace(Ne,"<$1></$2>")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r<i;r++)Me(o[r],a[r]);if(t)if(n)for(o=o||ye(e),a=a||ye(s),r=0,i=o.length;r<i;r++)Pe(o[r],a[r]);else Pe(e,s);return(a=ye(s,"script")).length>0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(w.cleanData(ye(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=[];return Re(this,arguments,function(t){var n=this.parentNode;w.inArray(this,e)<0&&(w.cleanData(ye(this)),n&&n.replaceChild(t,this))},e)}}),w.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){w.fn[e]=function(e){for(var n,r=[],i=w(e),o=i.length-1,a=0;a<=o;a++)n=a===o?this:this.clone(!0),w(i[a])[t](n),s.apply(r,n.get());return this.pushStack(r)}});var We=new RegExp("^("+re+")(?!px)[a-z%]+$","i"),$e=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)},Be=new RegExp(oe.join("|"),"i");!function(){function t(){if(c){l.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",c.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",be.appendChild(l).appendChild(c);var t=e.getComputedStyle(c);i="1%"!==t.top,u=12===n(t.marginLeft),c.style.right="60%",s=36===n(t.right),o=36===n(t.width),c.style.position="absolute",a=36===c.offsetWidth||"absolute",be.removeChild(l),c=null}}function n(e){return Math.round(parseFloat(e))}var i,o,a,s,u,l=r.createElement("div"),c=r.createElement("div");c.style&&(c.style.backgroundClip="content-box",c.cloneNode(!0).style.backgroundClip="",h.clearCloneStyle="content-box"===c.style.backgroundClip,w.extend(h,{boxSizingReliable:function(){return t(),o},pixelBoxStyles:function(){return t(),s},pixelPosition:function(){return t(),i},reliableMarginLeft:function(){return t(),u},scrollboxSize:function(){return t(),a}}))}();function Fe(e,t,n){var r,i,o,a,s=e.style;return(n=n||$e(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||w.contains(e.ownerDocument,e)||(a=w.style(e,t)),!h.pixelBoxStyles()&&We.test(a)&&Be.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function _e(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}var ze=/^(none|table(?!-c[ea]).+)/,Xe=/^--/,Ue={position:"absolute",visibility:"hidden",display:"block"},Ve={letterSpacing:"0",fontWeight:"400"},Ge=["Webkit","Moz","ms"],Ye=r.createElement("div").style;function Qe(e){if(e in Ye)return e;var t=e[0].toUpperCase()+e.slice(1),n=Ge.length;while(n--)if((e=Ge[n]+t)in Ye)return e}function Je(e){var t=w.cssProps[e];return t||(t=w.cssProps[e]=Qe(e)||e),t}function Ke(e,t,n){var r=ie.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ze(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=w.css(e,n+oe[a],!0,i)),r?("content"===n&&(u-=w.css(e,"padding"+oe[a],!0,i)),"margin"!==n&&(u-=w.css(e,"border"+oe[a]+"Width",!0,i))):(u+=w.css(e,"padding"+oe[a],!0,i),"padding"!==n?u+=w.css(e,"border"+oe[a]+"Width",!0,i):s+=w.css(e,"border"+oe[a]+"Width",!0,i));return!r&&o>=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a<i;a++)o[t[a]]=w.css(e,t[a],!1,r);return o}return void 0!==n?w.style(e,t,n):w.css(e,t)},e,t,arguments.length>1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function ct(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),y=J.get(e,"fxshow");n.queue||(null==(a=w._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,w.queue(e,"fx").length||a.empty.fire()})}));for(r in t)if(i=t[r],it.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!y||void 0===y[r])continue;g=!0}d[r]=y&&y[r]||w.style(e,r)}if((u=!w.isEmptyObject(t))||!w.isEmptyObject(d)){f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=y&&y.display)&&(l=J.get(e,"display")),"none"===(c=w.css(e,"display"))&&(l?c=l:(fe([e],!0),l=e.style.display||l,c=w.css(e,"display"),fe([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===w.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1;for(r in d)u||(y?"hidden"in y&&(g=y.hidden):y=J.access(e,"fxshow",{display:l}),o&&(y.hidden=!g),g&&fe([e],!0),p.done(function(){g||fe([e]),J.remove(e,"fxshow");for(r in d)w.style(e,r,d[r])})),u=lt(g?y[r]:0,r,p),r in y||(y[r]=u.start,g&&(u.end=u.start,u.start=0))}}function ft(e,t){var n,r,i,o,a;for(n in e)if(r=G(n),i=t[r],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=w.cssHooks[r])&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function pt(e,t,n){var r,i,o=0,a=pt.prefilters.length,s=w.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=nt||st(),n=Math.max(0,l.startTime+l.duration-t),r=1-(n/l.duration||0),o=0,a=l.tweens.length;o<a;o++)l.tweens[o].run(r);return s.notifyWith(e,[l,r,n]),r<1&&a?n:(a||s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:w.extend({},t),opts:w.extend(!0,{specialEasing:{},easing:w.easing._default},n),originalProperties:t,originalOptions:n,startTime:nt||st(),duration:n.duration,tweens:[],createTween:function(t,n){var r=w.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;n<r;n++)l.tweens[n].run(1);return t?(s.notifyWith(e,[l,1,0]),s.resolveWith(e,[l,t])):s.rejectWith(e,[l,t]),this}}),c=l.props;for(ft(c,l.opts.specialEasing);o<a;o++)if(r=pt.prefilters[o].call(l,e,c,l.opts))return g(r.stop)&&(w._queueHooks(l.elem,l.opts.queue).stop=r.stop.bind(r)),r;return w.map(c,lt,l),g(l.opts.start)&&l.opts.start.call(e,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),w.fx.timer(w.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l}w.Animation=w.extend(pt,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return ue(n.elem,e,ie.exec(t),n),n}]},tweener:function(e,t){g(e)?(t=e,e=["*"]):e=e.match(M);for(var n,r=0,i=e.length;r<i;r++)n=e[r],pt.tweeners[n]=pt.tweeners[n]||[],pt.tweeners[n].unshift(t)},prefilters:[ct],prefilter:function(e,t){t?pt.prefilters.unshift(e):pt.prefilters.push(e)}}),w.speed=function(e,t,n){var r=e&&"object"==typeof e?w.extend({},e):{complete:n||!n&&t||g(e)&&e,duration:e,easing:n&&t||t&&!g(t)&&t};return w.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in w.fx.speeds?r.duration=w.fx.speeds[r.duration]:r.duration=w.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){g(r.old)&&r.old.call(this),r.queue&&w.dequeue(this,r.queue)},r},w.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=w.isEmptyObject(e),o=w.speed(t,n,r),a=function(){var t=pt(this,w.extend({},e),o);(i||J.get(this,"finish"))&&t.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(e,t,n){var r=function(e){var t=e.stop;delete e.stop,t(n)};return"string"!=typeof e&&(n=t,t=e,e=void 0),t&&!1!==e&&this.queue(e||"fx",[]),this.each(function(){var t=!0,i=null!=e&&e+"queueHooks",o=w.timers,a=J.get(this);if(i)a[i]&&a[i].stop&&r(a[i]);else for(i in a)a[i]&&a[i].stop&&ot.test(i)&&r(a[i]);for(i=o.length;i--;)o[i].elem!==this||null!=e&&o[i].queue!==e||(o[i].anim.stop(n),t=!1,o.splice(i,1));!t&&n||w.dequeue(this,e)})},finish:function(e){return!1!==e&&(e=e||"fx"),this.each(function(){var t,n=J.get(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=w.timers,a=r?r.length:0;for(n.finish=!0,w.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;t<a;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),w.each(["toggle","show","hide"],function(e,t){var n=w.fn[t];w.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ut(t,!0),e,r,i)}}),w.each({slideDown:ut("show"),slideUp:ut("hide"),slideToggle:ut("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){w.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),w.timers=[],w.fx.tick=function(){var e,t=0,n=w.timers;for(nt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||w.fx.stop(),nt=void 0},w.fx.timer=function(e){w.timers.push(e),w.fx.start()},w.fx.interval=13,w.fx.start=function(){rt||(rt=!0,at())},w.fx.stop=function(){rt=null},w.fx.speeds={slow:600,fast:200,_default:400},w.fn.delay=function(t,n){return t=w.fx?w.fx.speeds[t]||t:t,n=n||"fx",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e=r.createElement("input"),t=r.createElement("select").appendChild(r.createElement("option"));e.type="checkbox",h.checkOn=""!==e.value,h.optSelected=t.selected,(e=r.createElement("input")).value="t",e.type="radio",h.radioValue="t"===e.value}();var dt,ht=w.expr.attrHandle;w.fn.extend({attr:function(e,t){return z(this,w.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!N(n.parentNode,"optgroup"))){if(t=w(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=w.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=w.inArray(w.valHooks.option.get(r),o)>-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w("<script>").prop({charset:e.scriptCharset,src:e.url}).on("load error",n=function(e){t.remove(),n=null,e&&o("error"===e.type?404:200,e.type)}),r.head.appendChild(t[0])},abort:function(){n&&n()}}}});var Yt=[],Qt=/(=)\?(?=&|$)|\?\?/;w.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Yt.pop()||w.expando+"_"+Et++;return this[e]=!0,e}}),w.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,a,s=!1!==t.jsonp&&(Qt.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&Qt.test(t.data)&&"data");if(s||"jsonp"===t.dataTypes[0])return i=t.jsonpCallback=g(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(Qt,"$1"+i):!1!==t.jsonp&&(t.url+=(kt.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return a||w.error(i+" was not called"),a[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?w(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,Yt.push(i)),a&&g(o)&&o(a[0]),a=o=void 0}),"script"}),h.createHTMLDocument=function(){var e=r.implementation.createHTMLDocument("").body;return e.innerHTML="<form></form><form></form>",2===e.childNodes.length}(),w.parseHTML=function(e,t,n){if("string"!=typeof e)return[];"boolean"==typeof t&&(n=t,t=!1);var i,o,a;return t||(h.createHTMLDocument?((i=(t=r.implementation.createHTMLDocument("")).createElement("base")).href=r.location.href,t.head.appendChild(i)):t=r),o=A.exec(e),a=!n&&[],o?[t.createElement(o[1])]:(o=xe([e],t,a),a&&a.length&&w(a).remove(),w.merge([],o.childNodes))},w.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return s>-1&&(r=vt(e.slice(s)),e=e.slice(0,s)),g(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),a.length>0&&w.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?w("<div>").append(w.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},w.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){w.fn[t]=function(e){return this.on(t,e)}}),w.expr.pseudos.animated=function(e){return w.grep(w.timers,function(t){return e===t.elem}).length},w.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=w.css(e,"position"),f=w(e),p={};"static"===c&&(e.style.position="relative"),s=f.offset(),o=w.css(e,"top"),u=w.css(e,"left"),(l=("absolute"===c||"fixed"===c)&&(o+u).indexOf("auto")>-1)?(a=(r=f.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),g(t)&&(t=t.call(e,n,w.extend({},s))),null!=t.top&&(p.top=t.top-s.top+a),null!=t.left&&(p.left=t.left-s.left+i),"using"in t?t.using.call(e,p):f.css(p)}},w.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){w.offset.setOffset(this,e,t)});var t,n,r=this[0];if(r)return r.getClientRects().length?(t=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===w.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===w.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=w(e).offset()).top+=w.css(e,"borderTopWidth",!0),i.left+=w.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-w.css(r,"marginTop",!0),left:t.left-i.left-w.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===w.css(e,"position"))e=e.offsetParent;return e||be})}}),w.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n="pageYOffset"===t;w.fn[e]=function(r){return z(this,function(e,r,i){var o;if(y(e)?o=e:9===e.nodeType&&(o=e.defaultView),void 0===i)return o?o[t]:e[r];o?o.scrollTo(n?o.pageXOffset:i,n?i:o.pageYOffset):e[r]=i},e,r,arguments.length)}}),w.each(["top","left"],function(e,t){w.cssHooks[t]=_e(h.pixelPosition,function(e,n){if(n)return n=Fe(e,t),We.test(n)?w(e).position()[t]+"px":n})}),w.each({Height:"height",Width:"width"},function(e,t){w.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){w.fn[r]=function(i,o){var a=arguments.length&&(n||"boolean"!=typeof i),s=n||(!0===i||!0===o?"margin":"border");return z(this,function(t,n,i){var o;return y(t)?0===r.indexOf("outer")?t["inner"+e]:t.document.documentElement["client"+e]:9===t.nodeType?(o=t.documentElement,Math.max(t.body["scroll"+e],o["scroll"+e],t.body["offset"+e],o["offset"+e],o["client"+e])):void 0===i?w.css(t,n,s):w.style(t,n,i,s)},t,a?i:void 0,a)}})}),w.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){w.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),w.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.readyWait++:w.ready(!0)},w.isArray=Array.isArray,w.parseJSON=JSON.parse,w.nodeName=N,w.isFunction=g,w.isWindow=y,w.camelCase=G,w.type=x,w.now=Date.now,w.isNumeric=function(e){var t=w.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},"function"==typeof define&&define.amd&&define("jquery",[],function(){return w});var Jt=e.jQuery,Kt=e.$;return w.noConflict=function(t){return e.$===w&&(e.$=Kt),t&&e.jQuery===w&&(e.jQuery=Jt),w},t||(e.jQuery=e.$=w),w});
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/backbone.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/backbone.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/backbone.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/backbone.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/backbone.validations.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/backbone.validations.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/backbone.validations.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/backbone.validations.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/bootpag.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/bootpag.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/bootpag.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/bootpag.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/bootstrap-sheet.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/bootstrap-sheet.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/bootstrap-sheet.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/bootstrap-sheet.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/bootstrapx-clickover.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/bootstrapx-clickover.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/bootstrapx-clickover.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/bootstrapx-clickover.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/html5.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/html5.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/html5.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/html5.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/i18next.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/i18next.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/i18next.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/i18next.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/jquery.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/jquery.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/jquery.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/jquery.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/moment-with-locales.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/moment-with-locales.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/moment-with-locales.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/moment-with-locales.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/purl.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/purl.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/purl.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/purl.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/lib/underscore.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/lib/underscore.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/lib/underscore.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/lib/underscore.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/locale/en/messages.json b/perun-oidc-server-webapp/src/main/webapp/resources/js/locale/en/messages.json
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/locale/en/messages.json
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/locale/en/messages.json
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/locale/fr/messages.json b/perun-oidc-server-webapp/src/main/webapp/resources/js/locale/fr/messages.json
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/locale/fr/messages.json
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/locale/fr/messages.json
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/locale/sv/messages.json b/perun-oidc-server-webapp/src/main/webapp/resources/js/locale/sv/messages.json
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/locale/sv/messages.json
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/locale/sv/messages.json
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/locale/zh/messages.json b/perun-oidc-server-webapp/src/main/webapp/resources/js/locale/zh/messages.json
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/locale/zh/messages.json
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/locale/zh/messages.json
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/locale/zh_CN/messages.json b/perun-oidc-server-webapp/src/main/webapp/resources/js/locale/zh_CN/messages.json
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/locale/zh_CN/messages.json
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/locale/zh_CN/messages.json
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/locale/zh_TW/messages.json b/perun-oidc-server-webapp/src/main/webapp/resources/js/locale/zh_TW/messages.json
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/locale/zh_TW/messages.json
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/locale/zh_TW/messages.json
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/profile.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/profile.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/profile.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/profile.js
diff --git a/perun-oidc-server-webapp/src/main/webapp/resources/js/reg_form_select.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/reg_form_select.js
new file mode 100644
index 000000000..65fcc892b
--- /dev/null
+++ b/perun-oidc-server-webapp/src/main/webapp/resources/js/reg_form_select.js
@@ -0,0 +1,25 @@
+function filter() {
+    hideGroups();
+    $('.selectGroup').val("");
+    const vo = $("#selectVo").val();
+    if (vo !== "") {
+        showGroups();
+        $(".groupOption").each(function () {
+            const value = $(this).val();
+            if (value.startsWith(vo, 0)) {
+                $(this).show();
+            } else {
+                $(this).hide();
+            }
+        });
+    }
+}
+function showGroups() {
+    $(".selectGroup").show();
+}
+function hideGroups() {
+    $(".selectGroup").hide();
+}
+$(document).ready(function() {
+    $("#selectVo").val("");
+});
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/rsreg.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/rsreg.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/rsreg.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/rsreg.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/scope.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/scope.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/scope.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/scope.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/token.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/token.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/token.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/token.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/js/whitelist.js b/perun-oidc-server-webapp/src/main/webapp/resources/js/whitelist.js
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/js/whitelist.js
rename to perun-oidc-server-webapp/src/main/webapp/resources/js/whitelist.js
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/admin.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/admin.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/admin.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/admin.html
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/blacklist.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/blacklist.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/blacklist.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/blacklist.html
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/client.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/client.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/client.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/client.html
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/dynreg.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/dynreg.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/dynreg.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/dynreg.html
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/grant.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/grant.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/grant.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/grant.html
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/rsreg.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/rsreg.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/rsreg.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/rsreg.html
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/scope.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/scope.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/scope.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/scope.html
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/token.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/token.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/token.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/token.html
diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/whitelist.html b/perun-oidc-server-webapp/src/main/webapp/resources/template/whitelist.html
similarity index 100%
rename from openid-connect-server-webapp/src/main/webapp/resources/template/whitelist.html
rename to perun-oidc-server-webapp/src/main/webapp/resources/template/whitelist.html
diff --git a/openid-connect-server/.gitignore b/perun-oidc-server/.gitignore
similarity index 100%
rename from openid-connect-server/.gitignore
rename to perun-oidc-server/.gitignore
diff --git a/openid-connect-server/pom.xml b/perun-oidc-server/pom.xml
similarity index 65%
rename from openid-connect-server/pom.xml
rename to perun-oidc-server/pom.xml
index 716fb4d9d..60d49d5a0 100644
--- a/openid-connect-server/pom.xml
+++ b/perun-oidc-server/pom.xml
@@ -18,23 +18,15 @@
  -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<modelVersion>4.0.0</modelVersion>
-	<artifactId>openid-connect-server</artifactId>
-	<name>OpenID Connect Server Library</name>
+
 	<parent>
-		<groupId>org.mitre</groupId>
-		<artifactId>openid-connect-parent</artifactId>
+		<groupId>cz.muni.ics</groupId>
+		<artifactId>perun-oidc-parent</artifactId>
 		<version>2.0.0</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-compiler-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
+	<artifactId>perun-oidc-server</artifactId>
 
 	<dependencies>
 		<dependency>
@@ -73,10 +65,6 @@
 			<groupId>commons-io</groupId>
 			<artifactId>commons-io</artifactId>
 		</dependency>
-		<dependency>
-			<groupId>ch.qos.logback</groupId>
-			<artifactId>logback-classic</artifactId>
-		</dependency>
 		<dependency>
 			<groupId>com.nimbusds</groupId>
 			<artifactId>nimbus-jose-jwt</artifactId>
@@ -97,10 +85,6 @@
 			<groupId>com.google.guava</groupId>
 			<artifactId>guava</artifactId>
 		</dependency>
-		<dependency>
-			<groupId>javax.servlet</groupId>
-			<artifactId>servlet-api</artifactId>
-		</dependency>
 		<dependency>
 			<groupId>org.apache.httpcomponents</groupId>
 			<artifactId>httpclient</artifactId>
@@ -127,8 +111,89 @@
 			<groupId>javax.annotation</groupId>
 			<artifactId>javax.annotation-api</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>jstl</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.dataformat</groupId>
+			<artifactId>jackson-dataformat-yaml</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.mariadb.jdbc</groupId>
+			<artifactId>mariadb-java-client</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.directory.api</groupId>
+			<artifactId>api-all</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.codehaus.janino</groupId>
+			<artifactId>janino</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>ch.qos.logback</groupId>
+			<artifactId>logback-classic</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.slf4j</groupId>
+					<artifactId>slf4j-api</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jul-to-slf4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.javacrumbs.shedlock</groupId>
+			<artifactId>shedlock-spring</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.javacrumbs.shedlock</groupId>
+			<artifactId>shedlock-provider-jdbc-template</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>jakarta.xml.bind</groupId>
+			<artifactId>jakarta.xml.bind-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.glassfish.jaxb</groupId>
+			<artifactId>jaxb-runtime</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>jakarta.servlet</groupId>
+			<artifactId>jakarta.servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-aop</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.aspectj</groupId>
+			<artifactId>aspectjweaver</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.security.extensions</groupId>
+			<artifactId>spring-security-saml2-core</artifactId>
+		</dependency>
 	</dependencies>
 
-	<description>OpenID Connect server libraries for Spring and Spring Security.</description>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+			</plugin>
+		</plugins>
+	</build>
 
 </project>
diff --git a/openid-connect-server/src/main/java/org/mitre/data/AbstractPageOperationTemplate.java b/perun-oidc-server/src/main/java/cz/muni/ics/data/AbstractPageOperationTemplate.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/data/AbstractPageOperationTemplate.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/data/AbstractPageOperationTemplate.java
index 54a35d952..9e6cb30ed 100644
--- a/openid-connect-server/src/main/java/org/mitre/data/AbstractPageOperationTemplate.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/data/AbstractPageOperationTemplate.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.data;
+package cz.muni.ics.data;
 
 import java.util.Collection;
 import java.util.HashSet;
diff --git a/openid-connect-server/src/main/java/org/mitre/data/DefaultPageCriteria.java b/perun-oidc-server/src/main/java/cz/muni/ics/data/DefaultPageCriteria.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/data/DefaultPageCriteria.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/data/DefaultPageCriteria.java
index 050f489ac..8efeb8e73 100644
--- a/openid-connect-server/src/main/java/org/mitre/data/DefaultPageCriteria.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/data/DefaultPageCriteria.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.data;
+package cz.muni.ics.data;
 
 /**
  * Default implementation of PageCriteria which specifies
diff --git a/openid-connect-server/src/main/java/org/mitre/data/PageCriteria.java b/perun-oidc-server/src/main/java/cz/muni/ics/data/PageCriteria.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/data/PageCriteria.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/data/PageCriteria.java
index 70e697684..10fa38c7b 100644
--- a/openid-connect-server/src/main/java/org/mitre/data/PageCriteria.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/data/PageCriteria.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.data;
+package cz.muni.ics.data;
 
 /**
  * Interface which defines page criteria for use in
diff --git a/openid-connect-server/src/main/java/org/mitre/discovery/util/WebfingerURLNormalizer.java b/perun-oidc-server/src/main/java/cz/muni/ics/discovery/util/WebfingerURLNormalizer.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/discovery/util/WebfingerURLNormalizer.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/discovery/util/WebfingerURLNormalizer.java
index c004d1134..70c5f01ce 100644
--- a/openid-connect-server/src/main/java/org/mitre/discovery/util/WebfingerURLNormalizer.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/discovery/util/WebfingerURLNormalizer.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.discovery.util;
+package cz.muni.ics.discovery.util;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
diff --git a/openid-connect-server/src/main/java/org/mitre/discovery/view/WebfingerView.java b/perun-oidc-server/src/main/java/cz/muni/ics/discovery/view/WebfingerView.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/discovery/view/WebfingerView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/discovery/view/WebfingerView.java
index 527ff4788..0c3888ebb 100644
--- a/openid-connect-server/src/main/java/org/mitre/discovery/view/WebfingerView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/discovery/view/WebfingerView.java
@@ -18,8 +18,9 @@
 /**
  *
  */
-package org.mitre.discovery.view;
+package cz.muni.ics.discovery.view;
 
+import cz.muni.ics.openid.connect.view.HttpCodeView;
 import java.io.IOException;
 import java.io.Writer;
 import java.util.Map;
@@ -27,7 +28,6 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.mitre.openid.connect.view.HttpCodeView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
diff --git a/openid-connect-server/src/main/java/org/mitre/discovery/web/DiscoveryEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/discovery/web/DiscoveryEndpoint.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/discovery/web/DiscoveryEndpoint.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/discovery/web/DiscoveryEndpoint.java
index a74633602..c69f1b358 100644
--- a/openid-connect-server/src/main/java/org/mitre/discovery/web/DiscoveryEndpoint.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/discovery/web/DiscoveryEndpoint.java
@@ -15,30 +15,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.discovery.web;
+package cz.muni.ics.discovery.web;
 
+import cz.muni.ics.discovery.util.WebfingerURLNormalizer;
+import cz.muni.ics.jwt.encryption.service.JWTEncryptionAndDecryptionService;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.openid.connect.web.DynamicClientRegistrationEndpoint;
+import cz.muni.ics.openid.connect.web.EndSessionEndpoint;
+import cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint;
+import cz.muni.ics.openid.connect.web.UserInfoEndpoint;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.mitre.discovery.util.WebfingerURLNormalizer;
-import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.oauth2.model.PKCEAlgorithm;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.oauth2.web.DeviceEndpoint;
-import org.mitre.oauth2.web.IntrospectionEndpoint;
-import org.mitre.oauth2.web.RevocationEndpoint;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.UserInfoService;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.openid.connect.web.DynamicClientRegistrationEndpoint;
-import org.mitre.openid.connect.web.EndSessionEndpoint;
-import org.mitre.openid.connect.web.JWKSetPublishingEndpoint;
-import org.mitre.openid.connect.web.UserInfoEndpoint;
+import cz.muni.ics.oauth2.model.PKCEAlgorithm;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.oauth2.web.DeviceEndpoint;
+import cz.muni.ics.oauth2.web.IntrospectionEndpoint;
+import cz.muni.ics.oauth2.web.RevocationEndpoint;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.UserInfoService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/jose/keystore/JWKSetKeyStore.java b/perun-oidc-server/src/main/java/cz/muni/ics/jose/keystore/JWKSetKeyStore.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/jose/keystore/JWKSetKeyStore.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jose/keystore/JWKSetKeyStore.java
index 37746f4c1..b2e8b2146 100644
--- a/openid-connect-server/src/main/java/org/mitre/jose/keystore/JWKSetKeyStore.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jose/keystore/JWKSetKeyStore.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.jose.keystore;
+package cz.muni.ics.jose.keystore;
 
 import com.nimbusds.jose.jwk.JWK;
 import com.nimbusds.jose.jwk.JWKSet;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/AbstractAssertionValidator.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/AbstractAssertionValidator.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/jwt/assertion/AbstractAssertionValidator.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/AbstractAssertionValidator.java
index d989b6010..599aeb610 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/AbstractAssertionValidator.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/AbstractAssertionValidator.java
@@ -1,4 +1,4 @@
-package org.mitre.jwt.assertion;
+package cz.muni.ics.jwt.assertion;
 
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/AssertionValidator.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/AssertionValidator.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/jwt/assertion/AssertionValidator.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/AssertionValidator.java
index 9b1e4e476..5e406831d 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/AssertionValidator.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/AssertionValidator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.jwt.assertion;
+package cz.muni.ics.jwt.assertion;
 
 import com.nimbusds.jwt.JWT;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/NullAssertionValidator.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/NullAssertionValidator.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/NullAssertionValidator.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/NullAssertionValidator.java
index 55c443a4d..df6083e23 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/NullAssertionValidator.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/NullAssertionValidator.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.jwt.assertion.impl;
+package cz.muni.ics.jwt.assertion.impl;
 
-import org.mitre.jwt.assertion.AssertionValidator;
+import cz.muni.ics.jwt.assertion.AssertionValidator;
 
 import com.nimbusds.jwt.JWT;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/SelfAssertionValidator.java
similarity index 87%
rename from openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/SelfAssertionValidator.java
index df028daa0..86e9f8220 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/SelfAssertionValidator.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.jwt.assertion.impl;
+package cz.muni.ics.jwt.assertion.impl;
 
-import org.mitre.jwt.assertion.AbstractAssertionValidator;
-import org.mitre.jwt.assertion.AssertionValidator;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.jwt.assertion.AbstractAssertionValidator;
+import cz.muni.ics.jwt.assertion.AssertionValidator;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java
index ad8d37c8e..ce4086d83 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.jwt.assertion.impl;
+package cz.muni.ics.jwt.assertion.impl;
 
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.SignedJWT;
-import org.mitre.jwt.assertion.AbstractAssertionValidator;
-import org.mitre.jwt.assertion.AssertionValidator;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.jwt.signer.service.impl.JWKSetCacheService;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.jwt.assertion.AbstractAssertionValidator;
+import cz.muni.ics.jwt.assertion.AssertionValidator;
+import cz.muni.ics.jwt.signer.service.impl.JWKSetCacheService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.StringUtils;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/encryption/service/JWTEncryptionAndDecryptionService.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/encryption/service/JWTEncryptionAndDecryptionService.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/jwt/encryption/service/JWTEncryptionAndDecryptionService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/encryption/service/JWTEncryptionAndDecryptionService.java
index baf7860ce..b3aeca203 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/encryption/service/JWTEncryptionAndDecryptionService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/encryption/service/JWTEncryptionAndDecryptionService.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.jwt.encryption.service;
+package cz.muni.ics.jwt.encryption.service;
 
 import java.util.Collection;
 import java.util.Map;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java
index 01213faaf..9d11eaad9 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java
@@ -15,8 +15,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.jwt.encryption.service.impl;
+package cz.muni.ics.jwt.encryption.service.impl;
 
+import cz.muni.ics.jose.keystore.JWKSetKeyStore;
+import cz.muni.ics.jwt.encryption.service.JWTEncryptionAndDecryptionService;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -26,8 +28,6 @@ import java.util.Set;
 import javax.annotation.PostConstruct;
 
 import com.nimbusds.jose.KeyLengthException;
-import org.mitre.jose.keystore.JWKSetKeyStore;
-import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/JWTSigningAndValidationService.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/JWTSigningAndValidationService.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/jwt/signer/service/JWTSigningAndValidationService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/JWTSigningAndValidationService.java
index 582e2c39a..abac18917 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/JWTSigningAndValidationService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/JWTSigningAndValidationService.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.jwt.signer.service;
+package cz.muni.ics.jwt.signer.service;
 
 import java.security.NoSuchAlgorithmException;
 import java.util.Collection;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/ClientKeyCacheService.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/ClientKeyCacheService.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/ClientKeyCacheService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/ClientKeyCacheService.java
index 5f6866ff6..2e10c0e4f 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/ClientKeyCacheService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/ClientKeyCacheService.java
@@ -14,19 +14,19 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.jwt.signer.service.impl;
+package cz.muni.ics.jwt.signer.service.impl;
 
+import cz.muni.ics.jose.keystore.JWKSetKeyStore;
+import cz.muni.ics.jwt.encryption.service.JWTEncryptionAndDecryptionService;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
-import org.mitre.jose.keystore.JWKSetKeyStore;
-import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
-import org.mitre.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java
index 34a97ff30..0d4ffc083 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.jwt.signer.service.impl;
+package cz.muni.ics.jwt.signer.service.impl;
 
 import com.nimbusds.jose.JOSEException;
 
@@ -34,8 +34,8 @@ import com.nimbusds.jose.jwk.JWK;
 import com.nimbusds.jose.jwk.OctetSequenceKey;
 import com.nimbusds.jose.jwk.RSAKey;
 import com.nimbusds.jwt.SignedJWT;
-import org.mitre.jose.keystore.JWKSetKeyStore;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.jose.keystore.JWKSetKeyStore;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.StringUtils;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/JWKSetCacheService.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/JWKSetCacheService.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/JWKSetCacheService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/JWKSetCacheService.java
index 227550c96..b4bf5979a 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/JWKSetCacheService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/JWKSetCacheService.java
@@ -18,17 +18,17 @@
 /**
  *
  */
-package org.mitre.jwt.signer.service.impl;
+package cz.muni.ics.jwt.signer.service.impl;
 
+import cz.muni.ics.jose.keystore.JWKSetKeyStore;
+import cz.muni.ics.jwt.encryption.service.JWTEncryptionAndDecryptionService;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.http.client.HttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
-import org.mitre.jose.keystore.JWKSetKeyStore;
-import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
-import org.mitre.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
diff --git a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java
index 3a40559c5..b88bab051 100644
--- a/openid-connect-server/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.jwt.signer.service.impl;
+package cz.muni.ics.jwt.signer.service.impl;
 
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
@@ -24,8 +24,8 @@ import com.nimbusds.jose.jwk.JWK;
 import com.nimbusds.jose.jwk.KeyUse;
 import com.nimbusds.jose.jwk.OctetSequenceKey;
 import com.nimbusds.jose.util.Base64URL;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
diff --git a/openid-connect-server/src/main/java/org/mitre/mdc/MultiMDCFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/mdc/MultiMDCFilter.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/mdc/MultiMDCFilter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/mdc/MultiMDCFilter.java
index 27850a2e4..8f8b6b00c 100644
--- a/openid-connect-server/src/main/java/org/mitre/mdc/MultiMDCFilter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/mdc/MultiMDCFilter.java
@@ -1,4 +1,4 @@
-package org.mitre.mdc;
+package cz.muni.ics.mdc;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/openid-connect-server/src/main/java/org/mitre/mdc/RemoteAddressMDCFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/mdc/RemoteAddressMDCFilter.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/mdc/RemoteAddressMDCFilter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/mdc/RemoteAddressMDCFilter.java
index 45b6beb46..82aa2e92c 100644
--- a/openid-connect-server/src/main/java/org/mitre/mdc/RemoteAddressMDCFilter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/mdc/RemoteAddressMDCFilter.java
@@ -1,4 +1,4 @@
-package org.mitre.mdc;
+package cz.muni.ics.mdc;
 
 import org.slf4j.MDC;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/mdc/SessionIdMDCFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/mdc/SessionIdMDCFilter.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/mdc/SessionIdMDCFilter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/mdc/SessionIdMDCFilter.java
index 85f95de87..563aff710 100644
--- a/openid-connect-server/src/main/java/org/mitre/mdc/SessionIdMDCFilter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/mdc/SessionIdMDCFilter.java
@@ -1,4 +1,4 @@
-package org.mitre.mdc;
+package cz.muni.ics.mdc;
 
 import org.slf4j.MDC;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/AssertionOAuth2RequestFactory.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/assertion/AssertionOAuth2RequestFactory.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/assertion/AssertionOAuth2RequestFactory.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/assertion/AssertionOAuth2RequestFactory.java
index 729d8a30d..e27e7f21d 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/AssertionOAuth2RequestFactory.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/assertion/AssertionOAuth2RequestFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.assertion;
+package cz.muni.ics.oauth2.assertion;
 
 import org.springframework.security.oauth2.provider.ClientDetails;
 import org.springframework.security.oauth2.provider.OAuth2Request;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/impl/DirectCopyRequestFactory.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/assertion/impl/DirectCopyRequestFactory.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/assertion/impl/DirectCopyRequestFactory.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/assertion/impl/DirectCopyRequestFactory.java
index 31a7bbd4e..a74bbbeaf 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/impl/DirectCopyRequestFactory.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/assertion/impl/DirectCopyRequestFactory.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.assertion.impl;
+package cz.muni.ics.oauth2.assertion.impl;
 
 import java.text.ParseException;
 import java.util.Set;
 
-import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory;
+import cz.muni.ics.oauth2.assertion.AssertionOAuth2RequestFactory;
 import org.springframework.security.oauth2.common.util.OAuth2Utils;
 import org.springframework.security.oauth2.provider.ClientDetails;
 import org.springframework.security.oauth2.provider.OAuth2Request;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/AuthorizationPendingException.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/AuthorizationPendingException.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/exception/AuthorizationPendingException.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/AuthorizationPendingException.java
index 20ba04b8a..fa10c5553 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/AuthorizationPendingException.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/AuthorizationPendingException.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.exception;
+package cz.muni.ics.oauth2.exception;
 
 import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeCreationException.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DeviceCodeCreationException.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeCreationException.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DeviceCodeCreationException.java
index dd3bd389f..36700259a 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeCreationException.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DeviceCodeCreationException.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.exception;
+package cz.muni.ics.oauth2.exception;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeExpiredException.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DeviceCodeExpiredException.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeExpiredException.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DeviceCodeExpiredException.java
index 006760ace..f064a0ec3 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeExpiredException.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DeviceCodeExpiredException.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.exception;
+package cz.muni.ics.oauth2.exception;
 
 import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DuplicateClientIdException.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DuplicateClientIdException.java
index 8e01c86bc..c269df5d6 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/exception/DuplicateClientIdException.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.exception;
+package cz.muni.ics.oauth2.exception;
 
 public class DuplicateClientIdException extends RuntimeException {
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/AuthenticationHolderEntity.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/AuthenticationHolderEntity.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/AuthenticationHolderEntity.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/AuthenticationHolderEntity.java
index e46e8ceb9..936ccc0df 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/AuthenticationHolderEntity.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/AuthenticationHolderEntity.java
@@ -15,8 +15,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
+import cz.muni.ics.oauth2.model.convert.SerializableStringConverter;
+import cz.muni.ics.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.HashMap;
@@ -43,8 +45,6 @@ import javax.persistence.OneToOne;
 import javax.persistence.Table;
 import javax.persistence.Transient;
 
-import org.mitre.oauth2.model.convert.SerializableStringConverter;
-import org.mitre.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 import org.springframework.security.oauth2.provider.OAuth2Request;
@@ -186,7 +186,7 @@ public class AuthenticationHolderEntity {
 	@CollectionTable(name="authentication_holder_extension", joinColumns=@JoinColumn(name="owner_id"))
 	@Column(name="val")
 	@MapKeyColumn(name="extension")
-	@Convert(converter=SerializableStringConverter.class)
+	@Convert(converter= SerializableStringConverter.class)
 	public Map<String, Serializable> getExtensions() {
 		return extensions;
 	}
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/AuthorizationCodeEntity.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/AuthorizationCodeEntity.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/AuthorizationCodeEntity.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/AuthorizationCodeEntity.java
index 98f697af0..be48f34f5 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/AuthorizationCodeEntity.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/AuthorizationCodeEntity.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import java.util.Date;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/ClientDetailsEntity.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/ClientDetailsEntity.java
index 08c716002..330db1dc7 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/ClientDetailsEntity.java
@@ -18,20 +18,20 @@
 /**
  *
  */
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import com.nimbusds.jose.EncryptionMethod;
 import com.nimbusds.jose.JWEAlgorithm;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jwt.JWT;
-import org.mitre.oauth2.model.convert.JWEAlgorithmStringConverter;
-import org.mitre.oauth2.model.convert.JWEEncryptionMethodStringConverter;
-import org.mitre.oauth2.model.convert.JWKSetStringConverter;
-import org.mitre.oauth2.model.convert.JWSAlgorithmStringConverter;
-import org.mitre.oauth2.model.convert.JWTStringConverter;
-import org.mitre.oauth2.model.convert.PKCEAlgorithmStringConverter;
-import org.mitre.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
+import cz.muni.ics.oauth2.model.convert.JWEAlgorithmStringConverter;
+import cz.muni.ics.oauth2.model.convert.JWEEncryptionMethodStringConverter;
+import cz.muni.ics.oauth2.model.convert.JWKSetStringConverter;
+import cz.muni.ics.oauth2.model.convert.JWSAlgorithmStringConverter;
+import cz.muni.ics.oauth2.model.convert.JWTStringConverter;
+import cz.muni.ics.oauth2.model.convert.PKCEAlgorithmStringConverter;
+import cz.muni.ics.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.oauth2.provider.ClientDetails;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/DeviceCode.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/DeviceCode.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/DeviceCode.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/DeviceCode.java
index b34a33ed2..bbf864df3 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/DeviceCode.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/DeviceCode.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import java.util.Date;
 import java.util.Map;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/OAuth2AccessTokenEntity.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/OAuth2AccessTokenEntity.java
index e702910db..811a49e88 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/OAuth2AccessTokenEntity.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import java.util.Date;
 import java.util.HashMap;
@@ -46,9 +46,9 @@ import javax.persistence.Table;
 import javax.persistence.Temporal;
 import javax.persistence.Transient;
 
-import org.mitre.oauth2.model.convert.JWTStringConverter;
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.uma.model.Permission;
+import cz.muni.ics.oauth2.model.convert.JWTStringConverter;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.uma.model.Permission;
 import org.springframework.security.oauth2.common.OAuth2AccessToken;
 import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer;
 import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/OAuth2RefreshTokenEntity.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/OAuth2RefreshTokenEntity.java
index a1bd2288c..67f98f808 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/OAuth2RefreshTokenEntity.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import java.util.Date;
 
@@ -38,7 +38,7 @@ import javax.persistence.Table;
 import javax.persistence.Temporal;
 import javax.persistence.Transient;
 
-import org.mitre.oauth2.model.convert.JWTStringConverter;
+import cz.muni.ics.oauth2.model.convert.JWTStringConverter;
 import org.springframework.security.oauth2.common.OAuth2RefreshToken;
 
 import com.nimbusds.jwt.JWT;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/PKCEAlgorithm.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/PKCEAlgorithm.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/PKCEAlgorithm.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/PKCEAlgorithm.java
index ab86effcc..c52b7be1e 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/PKCEAlgorithm.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/PKCEAlgorithm.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import com.nimbusds.jose.Algorithm;
 import com.nimbusds.jose.Requirement;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/RegisteredClient.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/RegisteredClient.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/RegisteredClient.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/RegisteredClient.java
index 454e702b9..0a3f2efa2 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/RegisteredClient.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/RegisteredClient.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import com.google.gson.JsonObject;
 import com.nimbusds.jose.EncryptionMethod;
@@ -26,9 +26,6 @@ import com.nimbusds.jose.JWEAlgorithm;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jwt.JWT;
-import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
-import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
-import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
 import org.springframework.security.core.GrantedAuthority;
 
 import java.util.Date;
@@ -205,11 +202,11 @@ public class RegisteredClient {
 		return client.getAdditionalInformation();
 	}
 
-	public AppType getApplicationType() {
+	public ClientDetailsEntity.AppType getApplicationType() {
 		return client.getApplicationType();
 	}
 
-	public void setApplicationType(AppType applicationType) {
+	public void setApplicationType(ClientDetailsEntity.AppType applicationType) {
 		client.setApplicationType(applicationType);
 	}
 
@@ -221,19 +218,19 @@ public class RegisteredClient {
 		client.setClientName(clientName);
 	}
 
-	public AuthMethod getTokenEndpointAuthMethod() {
+	public ClientDetailsEntity.AuthMethod getTokenEndpointAuthMethod() {
 		return client.getTokenEndpointAuthMethod();
 	}
 
-	public void setTokenEndpointAuthMethod(AuthMethod tokenEndpointAuthMethod) {
+	public void setTokenEndpointAuthMethod(ClientDetailsEntity.AuthMethod tokenEndpointAuthMethod) {
 		client.setTokenEndpointAuthMethod(tokenEndpointAuthMethod);
 	}
 
-	public SubjectType getSubjectType() {
+	public ClientDetailsEntity.SubjectType getSubjectType() {
 		return client.getSubjectType();
 	}
 
-	public void setSubjectType(SubjectType subjectType) {
+	public void setSubjectType(ClientDetailsEntity.SubjectType subjectType) {
 		client.setSubjectType(subjectType);
 	}
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/RegisteredClientFields.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/RegisteredClientFields.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/RegisteredClientFields.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/RegisteredClientFields.java
index b334c4827..8803ec74b 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/RegisteredClientFields.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/RegisteredClientFields.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 public interface RegisteredClientFields {
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/SavedUserAuthentication.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/SavedUserAuthentication.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/SavedUserAuthentication.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/SavedUserAuthentication.java
index dfe4829e8..b085e5d0c 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/SavedUserAuthentication.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/SavedUserAuthentication.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
+import cz.muni.ics.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
 import java.util.Collection;
 import java.util.HashSet;
 
@@ -33,7 +34,6 @@ import javax.persistence.JoinColumn;
 import javax.persistence.Table;
 import javax.persistence.Transient;
 
-import org.mitre.oauth2.model.convert.SimpleGrantedAuthorityStringConverter;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/SystemScope.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/SystemScope.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/SystemScope.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/SystemScope.java
index 78f858dbc..70fd23476 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/SystemScope.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/SystemScope.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import javax.persistence.Basic;
 import javax.persistence.Column;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWEAlgorithmStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWEAlgorithmStringConverter.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWEAlgorithmStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWEAlgorithmStringConverter.java
index 729e36b66..bbb562dc0 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWEAlgorithmStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWEAlgorithmStringConverter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWEEncryptionMethodStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWEEncryptionMethodStringConverter.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWEEncryptionMethodStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWEEncryptionMethodStringConverter.java
index 21a50fd62..3d154cdba 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWEEncryptionMethodStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWEEncryptionMethodStringConverter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWKSetStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWKSetStringConverter.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWKSetStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWKSetStringConverter.java
index e3f8e7bde..0e23e1a9a 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWKSetStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWKSetStringConverter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import java.text.ParseException;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWSAlgorithmStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWSAlgorithmStringConverter.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWSAlgorithmStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWSAlgorithmStringConverter.java
index 6d35d0044..c5e31e52b 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWSAlgorithmStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWSAlgorithmStringConverter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWTStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWTStringConverter.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWTStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWTStringConverter.java
index dfae2cf30..f9b79f2c9 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JWTStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JWTStringConverter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import java.text.ParseException;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JsonElementStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JsonElementStringConverter.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JsonElementStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JsonElementStringConverter.java
index 7a8010c3a..250c6e2b0 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/JsonElementStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/JsonElementStringConverter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/PKCEAlgorithmStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/PKCEAlgorithmStringConverter.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/PKCEAlgorithmStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/PKCEAlgorithmStringConverter.java
index 5fa2b2608..a201f07fe 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/PKCEAlgorithmStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/PKCEAlgorithmStringConverter.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
 
-import org.mitre.oauth2.model.PKCEAlgorithm;
+import cz.muni.ics.oauth2.model.PKCEAlgorithm;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/SerializableStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/SerializableStringConverter.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/SerializableStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/SerializableStringConverter.java
index 27dfdccef..5844cf905 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/SerializableStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/SerializableStringConverter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import java.io.Serializable;
 import java.util.Date;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java
index 6ff460bcb..3e867ca72 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.model.convert;
+package cz.muni.ics.oauth2.model.convert;
 
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/AuthenticationHolderRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/AuthenticationHolderRepository.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/AuthenticationHolderRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/AuthenticationHolderRepository.java
index 9666d910f..2d7b50405 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/AuthenticationHolderRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/AuthenticationHolderRepository.java
@@ -15,12 +15,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.repository;
+package cz.muni.ics.oauth2.repository;
 
+import cz.muni.ics.data.PageCriteria;
 import java.util.List;
 
-import org.mitre.data.PageCriteria;
-import org.mitre.oauth2.model.AuthenticationHolderEntity;
+import cz.muni.ics.oauth2.model.AuthenticationHolderEntity;
 
 public interface AuthenticationHolderRepository {
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/AuthorizationCodeRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/AuthorizationCodeRepository.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/AuthorizationCodeRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/AuthorizationCodeRepository.java
index 0cff24b10..ab669cecd 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/AuthorizationCodeRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/AuthorizationCodeRepository.java
@@ -15,12 +15,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.repository;
+package cz.muni.ics.oauth2.repository;
 
+import cz.muni.ics.data.PageCriteria;
 import java.util.Collection;
 
-import org.mitre.data.PageCriteria;
-import org.mitre.oauth2.model.AuthorizationCodeEntity;
+import cz.muni.ics.oauth2.model.AuthorizationCodeEntity;
 
 /**
  * Interface for saving and consuming OAuth2 authorization codes as AuthorizationCodeEntitys.
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/OAuth2ClientRepository.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/OAuth2ClientRepository.java
index 00c70ee34..5288528af 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/OAuth2ClientRepository.java
@@ -15,11 +15,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.repository;
+package cz.muni.ics.oauth2.repository;
 
 import java.util.Collection;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
 
 public interface OAuth2ClientRepository {
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/OAuth2TokenRepository.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/OAuth2TokenRepository.java
index 8465fcb0d..80032c7e3 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/OAuth2TokenRepository.java
@@ -15,17 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.repository;
+package cz.muni.ics.oauth2.repository;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
 import java.util.List;
 import java.util.Set;
 
-import org.mitre.data.PageCriteria;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.uma.model.ResourceSet;
+import cz.muni.ics.data.PageCriteria;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.uma.model.ResourceSet;
 
 public interface OAuth2TokenRepository {
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/SystemScopeRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/SystemScopeRepository.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/SystemScopeRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/SystemScopeRepository.java
index 022ef8388..ccd745f4f 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/SystemScopeRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/SystemScopeRepository.java
@@ -18,11 +18,11 @@
 /**
  *
  */
-package org.mitre.oauth2.repository;
+package cz.muni.ics.oauth2.repository;
 
 import java.util.Set;
 
-import org.mitre.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.model.SystemScope;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/DeviceCodeRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/DeviceCodeRepository.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/DeviceCodeRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/DeviceCodeRepository.java
index 94d408689..10bc0c604 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/DeviceCodeRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/DeviceCodeRepository.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.repository.impl;
+package cz.muni.ics.oauth2.repository.impl;
 
 import java.util.Collection;
 
-import org.mitre.oauth2.model.DeviceCode;
+import cz.muni.ics.oauth2.model.DeviceCode;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthenticationHolderRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaAuthenticationHolderRepository.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthenticationHolderRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaAuthenticationHolderRepository.java
index 7dcbef06d..7f765405b 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthenticationHolderRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaAuthenticationHolderRepository.java
@@ -15,19 +15,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.repository.impl;
+package cz.muni.ics.oauth2.repository.impl;
 
+import cz.muni.ics.data.DefaultPageCriteria;
+import cz.muni.ics.data.PageCriteria;
+import cz.muni.ics.util.jpa.JpaUtil;
 import java.util.List;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.data.DefaultPageCriteria;
-import org.mitre.data.PageCriteria;
-import org.mitre.oauth2.model.AuthenticationHolderEntity;
-import org.mitre.oauth2.repository.AuthenticationHolderRepository;
-import org.mitre.util.jpa.JpaUtil;
+import cz.muni.ics.oauth2.model.AuthenticationHolderEntity;
+import cz.muni.ics.oauth2.repository.AuthenticationHolderRepository;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthorizationCodeRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaAuthorizationCodeRepository.java
similarity index 82%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthorizationCodeRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaAuthorizationCodeRepository.java
index 8209800d3..b2f202bf3 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthorizationCodeRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaAuthorizationCodeRepository.java
@@ -18,8 +18,11 @@
 /**
  *
  */
-package org.mitre.oauth2.repository.impl;
+package cz.muni.ics.oauth2.repository.impl;
 
+import cz.muni.ics.data.PageCriteria;
+import cz.muni.ics.oauth2.repository.AuthorizationCodeRepository;
+import cz.muni.ics.util.jpa.JpaUtil;
 import java.util.Collection;
 import java.util.Date;
 
@@ -27,10 +30,7 @@ import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.data.PageCriteria;
-import org.mitre.oauth2.model.AuthorizationCodeEntity;
-import org.mitre.oauth2.repository.AuthorizationCodeRepository;
-import org.mitre.util.jpa.JpaUtil;
+import cz.muni.ics.oauth2.model.AuthorizationCodeEntity;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -48,7 +48,7 @@ public class JpaAuthorizationCodeRepository implements AuthorizationCodeReposito
 	EntityManager manager;
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.AuthorizationCodeRepository#save(org.mitre.oauth2.model.AuthorizationCodeEntity)
+	 * @see cz.muni.ics.oauth2.repository.AuthorizationCodeRepository#save(cz.muni.ics.oauth2.model.AuthorizationCodeEntity)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -59,7 +59,7 @@ public class JpaAuthorizationCodeRepository implements AuthorizationCodeReposito
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.AuthorizationCodeRepository#getByCode(java.lang.String)
+	 * @see cz.muni.ics.oauth2.repository.AuthorizationCodeRepository#getByCode(java.lang.String)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -72,7 +72,7 @@ public class JpaAuthorizationCodeRepository implements AuthorizationCodeReposito
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.AuthorizationCodeRepository#remove(org.mitre.oauth2.model.AuthorizationCodeEntity)
+	 * @see cz.muni.ics.oauth2.repository.AuthorizationCodeRepository#remove(cz.muni.ics.oauth2.model.AuthorizationCodeEntity)
 	 */
 	@Override
 	public void remove(AuthorizationCodeEntity authorizationCodeEntity) {
@@ -83,7 +83,7 @@ public class JpaAuthorizationCodeRepository implements AuthorizationCodeReposito
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.AuthorizationCodeRepository#getExpiredCodes()
+	 * @see cz.muni.ics.oauth2.repository.AuthorizationCodeRepository#getExpiredCodes()
 	 */
 	@Override
 	public Collection<AuthorizationCodeEntity> getExpiredCodes() {
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaDeviceCodeRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaDeviceCodeRepository.java
similarity index 84%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaDeviceCodeRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaDeviceCodeRepository.java
index 50304adab..33535e993 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaDeviceCodeRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaDeviceCodeRepository.java
@@ -16,11 +16,9 @@
 /**
  *
  */
-package org.mitre.oauth2.repository.impl;
-
-import static org.mitre.util.jpa.JpaUtil.getSingleResult;
-import static org.mitre.util.jpa.JpaUtil.saveOrUpdate;
+package cz.muni.ics.oauth2.repository.impl;
 
+import cz.muni.ics.util.jpa.JpaUtil;
 import java.util.Collection;
 import java.util.Date;
 
@@ -28,7 +26,7 @@ import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.oauth2.model.DeviceCode;
+import cz.muni.ics.oauth2.model.DeviceCode;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -57,7 +55,7 @@ public class JpaDeviceCodeRepository implements DeviceCodeRepository {
 	public DeviceCode getByUserCode(String value) {
 		TypedQuery<DeviceCode> query = em.createNamedQuery(DeviceCode.QUERY_BY_USER_CODE, DeviceCode.class);
 		query.setParameter(DeviceCode.PARAM_USER_CODE, value);
-		return getSingleResult(query.getResultList());
+		return JpaUtil.getSingleResult(query.getResultList());
 	}
 
 	/* (non-Javadoc)
@@ -67,7 +65,7 @@ public class JpaDeviceCodeRepository implements DeviceCodeRepository {
 	public DeviceCode getByDeviceCode(String value) {
 		TypedQuery<DeviceCode> query = em.createNamedQuery(DeviceCode.QUERY_BY_DEVICE_CODE, DeviceCode.class);
 		query.setParameter(DeviceCode.PARAM_DEVICE_CODE, value);
-		return getSingleResult(query.getResultList());
+		return JpaUtil.getSingleResult(query.getResultList());
 	}
 
 	/* (non-Javadoc)
@@ -84,16 +82,16 @@ public class JpaDeviceCodeRepository implements DeviceCodeRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.SystemScopeRepository#save(org.mitre.oauth2.model.SystemScope)
+	 * @see cz.muni.ics.oauth2.repository.SystemScopeRepository#save(cz.muni.ics.oauth2.model.SystemScope)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
 	public DeviceCode save(DeviceCode scope) {
-		return saveOrUpdate(em, scope);
+		return JpaUtil.saveOrUpdate(em, scope);
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.impl.DeviceCodeRepository#getExpiredCodes()
+	 * @see cz.muni.ics.oauth2.repository.impl.DeviceCodeRepository#getExpiredCodes()
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaOAuth2ClientRepository.java
similarity index 84%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaOAuth2ClientRepository.java
index 7ac796d03..ea359a8bd 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaOAuth2ClientRepository.java
@@ -15,17 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.repository.impl;
+package cz.muni.ics.oauth2.repository.impl;
 
+import cz.muni.ics.util.jpa.JpaUtil;
 import java.util.Collection;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.repository.OAuth2ClientRepository;
-import org.mitre.util.jpa.JpaUtil;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.repository.OAuth2ClientRepository;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -54,7 +54,7 @@ public class JpaOAuth2ClientRepository implements OAuth2ClientRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.OAuth2ClientRepository#getClientById(java.lang.String)
+	 * @see cz.muni.ics.oauth2.repository.OAuth2ClientRepository#getClientById(java.lang.String)
 	 */
 	@Override
 	public ClientDetailsEntity getClientByClientId(String clientId) {
@@ -64,7 +64,7 @@ public class JpaOAuth2ClientRepository implements OAuth2ClientRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.OAuth2ClientRepository#saveClient(org.mitre.oauth2.model.ClientDetailsEntity)
+	 * @see cz.muni.ics.oauth2.repository.OAuth2ClientRepository#saveClient(cz.muni.ics.oauth2.model.ClientDetailsEntity)
 	 */
 	@Override
 	public ClientDetailsEntity saveClient(ClientDetailsEntity client) {
@@ -72,7 +72,7 @@ public class JpaOAuth2ClientRepository implements OAuth2ClientRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.OAuth2ClientRepository#deleteClient(org.mitre.oauth2.model.ClientDetailsEntity)
+	 * @see cz.muni.ics.oauth2.repository.OAuth2ClientRepository#deleteClient(cz.muni.ics.oauth2.model.ClientDetailsEntity)
 	 */
 	@Override
 	public void deleteClient(ClientDetailsEntity client) {
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaOAuth2TokenRepository.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaOAuth2TokenRepository.java
index 627dbde27..78578ba5e 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaOAuth2TokenRepository.java
@@ -15,8 +15,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.repository.impl;
+package cz.muni.ics.oauth2.repository.impl;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.oauth2.repository.OAuth2TokenRepository;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
@@ -33,15 +36,12 @@ import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaDelete;
 import javax.persistence.criteria.Root;
 
-import org.mitre.data.DefaultPageCriteria;
-import org.mitre.data.PageCriteria;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.repository.OAuth2TokenRepository;
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.uma.model.ResourceSet;
-import org.mitre.util.jpa.JpaUtil;
+import cz.muni.ics.data.DefaultPageCriteria;
+import cz.muni.ics.data.PageCriteria;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.uma.model.ResourceSet;
+import cz.muni.ics.util.jpa.JpaUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Repository;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaSystemScopeRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaSystemScopeRepository.java
similarity index 75%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaSystemScopeRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaSystemScopeRepository.java
index ca483cbed..6bca112d7 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaSystemScopeRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/repository/impl/JpaSystemScopeRepository.java
@@ -18,11 +18,9 @@
 /**
  *
  */
-package org.mitre.oauth2.repository.impl;
-
-import static org.mitre.util.jpa.JpaUtil.getSingleResult;
-import static org.mitre.util.jpa.JpaUtil.saveOrUpdate;
+package cz.muni.ics.oauth2.repository.impl;
 
+import cz.muni.ics.util.jpa.JpaUtil;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
@@ -30,8 +28,8 @@ import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.repository.SystemScopeRepository;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.repository.SystemScopeRepository;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -46,7 +44,7 @@ public class JpaSystemScopeRepository implements SystemScopeRepository {
 	private EntityManager em;
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.SystemScopeRepository#getAll()
+	 * @see cz.muni.ics.oauth2.repository.SystemScopeRepository#getAll()
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -57,7 +55,7 @@ public class JpaSystemScopeRepository implements SystemScopeRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.SystemScopeRepository#getById(java.lang.Long)
+	 * @see cz.muni.ics.oauth2.repository.SystemScopeRepository#getById(java.lang.Long)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -66,18 +64,18 @@ public class JpaSystemScopeRepository implements SystemScopeRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.SystemScopeRepository#getByValue(java.lang.String)
+	 * @see cz.muni.ics.oauth2.repository.SystemScopeRepository#getByValue(java.lang.String)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
 	public SystemScope getByValue(String value) {
 		TypedQuery<SystemScope> query = em.createNamedQuery(SystemScope.QUERY_BY_VALUE, SystemScope.class);
 		query.setParameter(SystemScope.PARAM_VALUE, value);
-		return getSingleResult(query.getResultList());
+		return JpaUtil.getSingleResult(query.getResultList());
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.SystemScopeRepository#remove(org.mitre.oauth2.model.SystemScope)
+	 * @see cz.muni.ics.oauth2.repository.SystemScopeRepository#remove(cz.muni.ics.oauth2.model.SystemScope)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -91,12 +89,12 @@ public class JpaSystemScopeRepository implements SystemScopeRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.repository.SystemScopeRepository#save(org.mitre.oauth2.model.SystemScope)
+	 * @see cz.muni.ics.oauth2.repository.SystemScopeRepository#save(cz.muni.ics.oauth2.model.SystemScope)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
 	public SystemScope save(SystemScope scope) {
-		return saveOrUpdate(em, scope);
+		return JpaUtil.saveOrUpdate(em, scope);
 	}
 
 }
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/ClientDetailsEntityService.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/ClientDetailsEntityService.java
index 08695c675..195aae139 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/ClientDetailsEntityService.java
@@ -15,11 +15,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service;
+package cz.muni.ics.oauth2.service;
 
 import java.util.Collection;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
 import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
 import org.springframework.security.oauth2.provider.ClientDetailsService;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/DeviceCodeService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/DeviceCodeService.java
similarity index 88%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/DeviceCodeService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/DeviceCodeService.java
index 85e6adf55..7d878dcc9 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/DeviceCodeService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/DeviceCodeService.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.service;
+package cz.muni.ics.oauth2.service;
 
+import cz.muni.ics.oauth2.exception.DeviceCodeCreationException;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.DeviceCode;
 import java.util.Map;
 import java.util.Set;
 
-import org.mitre.oauth2.exception.DeviceCodeCreationException;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.DeviceCode;
 import org.springframework.security.oauth2.provider.ClientDetails;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/IntrospectionResultAssembler.java
similarity index 91%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/IntrospectionResultAssembler.java
index 84b715c4d..869e8a8e4 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/IntrospectionResultAssembler.java
@@ -13,18 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service;
+package cz.muni.ics.oauth2.service;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.openid.connect.model.UserInfo;
 import java.text.SimpleDateFormat;
 import java.util.Map;
 import java.util.Set;
 
 import javax.swing.text.DateFormatter;
 
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.openid.connect.model.UserInfo;
-
 /**
  * Strategy interface for assembling a token introspection result.
  */
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/OAuth2TokenEntityService.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/OAuth2TokenEntityService.java
index 76af302e7..1cd636349 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/OAuth2TokenEntityService.java
@@ -15,14 +15,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service;
+package cz.muni.ics.oauth2.service;
 
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
 import java.util.List;
 import java.util.Set;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
 import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
@@ -30,7 +30,7 @@ import org.springframework.security.oauth2.provider.token.ResourceServerTokenSer
 public interface OAuth2TokenEntityService extends AuthorizationServerTokenServices, ResourceServerTokenServices {
 
 	@Override
-	OAuth2AccessTokenEntity readAccessToken(String accessTokenValue);
+    OAuth2AccessTokenEntity readAccessToken(String accessTokenValue);
 
 	OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue);
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/SystemScopeService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/SystemScopeService.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/SystemScopeService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/SystemScopeService.java
index c4e7f4561..6785682d8 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/SystemScopeService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/SystemScopeService.java
@@ -18,16 +18,13 @@
 /**
  *
  */
-package org.mitre.oauth2.service;
+package cz.muni.ics.oauth2.service;
 
+import cz.muni.ics.oauth2.model.SystemScope;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.mitre.oauth2.model.SystemScope;
-
-import com.google.common.collect.Sets;
-
 /**
  * @author jricher
  */
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/BlacklistAwareRedirectResolver.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/BlacklistAwareRedirectResolver.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/BlacklistAwareRedirectResolver.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/BlacklistAwareRedirectResolver.java
index 1218737f3..2afba1a3b 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/BlacklistAwareRedirectResolver.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/BlacklistAwareRedirectResolver.java
@@ -16,11 +16,11 @@
 /**
  *
  */
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.service.BlacklistedSiteService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.service.BlacklistedSiteService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -44,8 +44,6 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import static org.mitre.oauth2.model.ClientDetailsEntity.AppType.NATIVE;
-
 /**
  *
  * A redirect resolver that knows how to check against the blacklisted URIs
@@ -173,7 +171,7 @@ public class BlacklistAwareRedirectResolver implements RedirectResolver {
 		boolean userInfoMatch = isEqual(registeredRedirectUri.getUserInfo(), requestedRedirectUri.getUserInfo());
 		boolean hostMatch = hostMatches(registeredRedirectUri.getHost(), requestedRedirectUri.getHost());
 		boolean portMatch = true;
-		if (!NATIVE.equals(applicationType)) {
+		if (!ClientDetailsEntity.AppType.NATIVE.equals(applicationType)) {
 			portMatch = !matchPorts || registeredRedirectUri.getPort() == requestedRedirectUri.getPort();
 		}
 		boolean pathMatch = true;
@@ -228,7 +226,7 @@ public class BlacklistAwareRedirectResolver implements RedirectResolver {
 				if (this.matchSubdomains) {
 					redirectUriBuilder.host(requestedRedirectUri.getHost());
 				}
-				if (!this.matchPorts || NATIVE.equals(applicationType)) {
+				if (!this.matchPorts || ClientDetailsEntity.AppType.NATIVE.equals(applicationType)) {
 					redirectUriBuilder.port(requestedRedirectUri.getPort());
 				}
 				if (!this.strictMatch) {
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultClientUserDetailsService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultClientUserDetailsService.java
similarity index 91%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultClientUserDetailsService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultClientUserDetailsService.java
index 81e384352..8b2eda697 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultClientUserDetailsService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultClientUserDetailsService.java
@@ -15,11 +15,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.oauth2.service.impl.ServiceUtils;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultDeviceCodeService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultDeviceCodeService.java
similarity index 81%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultDeviceCodeService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultDeviceCodeService.java
index 29f4ec8d2..9efa1d6ef 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultDeviceCodeService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultDeviceCodeService.java
@@ -14,20 +14,20 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
+import cz.muni.ics.data.AbstractPageOperationTemplate;
+import cz.muni.ics.oauth2.model.AuthenticationHolderEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.DeviceCode;
+import cz.muni.ics.oauth2.repository.impl.DeviceCodeRepository;
 import java.util.Collection;
 import java.util.Date;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
-import org.mitre.data.AbstractPageOperationTemplate;
-import org.mitre.oauth2.model.AuthenticationHolderEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.DeviceCode;
-import org.mitre.oauth2.repository.impl.DeviceCodeRepository;
-import org.mitre.oauth2.service.DeviceCodeService;
+import cz.muni.ics.oauth2.service.DeviceCodeService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
 import org.springframework.security.oauth2.provider.ClientDetails;
@@ -48,7 +48,7 @@ public class DefaultDeviceCodeService implements DeviceCodeService {
 	private RandomValueStringGenerator randomGenerator = new RandomValueStringGenerator();
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.DeviceCodeService#save(org.mitre.oauth2.model.DeviceCode)
+	 * @see cz.muni.ics.oauth2.service.DeviceCodeService#save(cz.muni.ics.oauth2.model.DeviceCode)
 	 */
 	@Override
 	public DeviceCode createNewDeviceCode(Set<String> requestedScopes, ClientDetailsEntity client, Map<String, String> parameters) {
@@ -71,7 +71,7 @@ public class DefaultDeviceCodeService implements DeviceCodeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.DeviceCodeService#lookUpByUserCode(java.lang.String)
+	 * @see cz.muni.ics.oauth2.service.DeviceCodeService#lookUpByUserCode(java.lang.String)
 	 */
 	@Override
 	public DeviceCode lookUpByUserCode(String userCode) {
@@ -80,7 +80,7 @@ public class DefaultDeviceCodeService implements DeviceCodeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.DeviceCodeService#approveDeviceCode(org.mitre.oauth2.model.DeviceCode)
+	 * @see cz.muni.ics.oauth2.service.DeviceCodeService#approveDeviceCode(cz.muni.ics.oauth2.model.DeviceCode)
 	 */
 	@Override
 	public DeviceCode approveDeviceCode(DeviceCode dc, OAuth2Authentication auth) {
@@ -97,7 +97,7 @@ public class DefaultDeviceCodeService implements DeviceCodeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.DeviceCodeService#consumeDeviceCode(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails)
+	 * @see cz.muni.ics.oauth2.service.DeviceCodeService#consumeDeviceCode(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails)
 	 */
 	@Override
 	public DeviceCode findDeviceCode(String deviceCode, ClientDetails client) {
@@ -121,7 +121,7 @@ public class DefaultDeviceCodeService implements DeviceCodeService {
 	
 	
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.DeviceCodeService#clearExpiredDeviceCodes()
+	 * @see cz.muni.ics.oauth2.service.DeviceCodeService#clearExpiredDeviceCodes()
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -141,7 +141,7 @@ public class DefaultDeviceCodeService implements DeviceCodeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.DeviceCodeService#clearDeviceCode(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails)
+	 * @see cz.muni.ics.oauth2.service.DeviceCodeService#clearDeviceCode(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails)
 	 */
 	@Override
 	public void clearDeviceCode(String deviceCode, ClientDetails client) {
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultIntrospectionResultAssembler.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultIntrospectionResultAssembler.java
index ea36949fb..bff81d00f 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultIntrospectionResultAssembler.java
@@ -13,19 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
 import static com.google.common.collect.Maps.newLinkedHashMap;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.uma.model.Permission;
 import java.text.ParseException;
 import java.util.Map;
 import java.util.Set;
 
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.service.IntrospectionResultAssembler;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.uma.model.Permission;
+import cz.muni.ics.oauth2.service.IntrospectionResultAssembler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java
index b062a1a4a..5d222c1d0 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java
@@ -18,16 +18,16 @@
 /**
  *
  */
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
+import cz.muni.ics.data.AbstractPageOperationTemplate;
+import cz.muni.ics.oauth2.model.AuthenticationHolderEntity;
+import cz.muni.ics.oauth2.model.AuthorizationCodeEntity;
+import cz.muni.ics.oauth2.repository.AuthenticationHolderRepository;
+import cz.muni.ics.oauth2.repository.AuthorizationCodeRepository;
 import java.util.Collection;
 import java.util.Date;
 
-import org.mitre.data.AbstractPageOperationTemplate;
-import org.mitre.oauth2.model.AuthenticationHolderEntity;
-import org.mitre.oauth2.model.AuthorizationCodeEntity;
-import org.mitre.oauth2.repository.AuthenticationHolderRepository;
-import org.mitre.oauth2.repository.AuthorizationCodeRepository;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
index b730329e3..698712e8e 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
 import com.google.common.base.Strings;
 import com.google.common.cache.CacheBuilder;
@@ -24,23 +24,23 @@ import com.google.common.cache.LoadingCache;
 import com.google.common.util.concurrent.UncheckedExecutionException;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParser;
+import cz.muni.ics.oauth2.repository.OAuth2TokenRepository;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.http.client.HttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.repository.OAuth2ClientRepository;
-import org.mitre.oauth2.repository.OAuth2TokenRepository;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.model.WhitelistedSite;
-import org.mitre.openid.connect.service.ApprovedSiteService;
-import org.mitre.openid.connect.service.BlacklistedSiteService;
-import org.mitre.openid.connect.service.WhitelistedSiteService;
-import org.mitre.uma.model.ResourceSet;
-import org.mitre.uma.service.ResourceSetService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AuthMethod;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.repository.OAuth2ClientRepository;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.service.ApprovedSiteService;
+import cz.muni.ics.openid.connect.service.BlacklistedSiteService;
+import cz.muni.ics.openid.connect.service.WhitelistedSiteService;
+import cz.muni.ics.uma.model.ResourceSet;
+import cz.muni.ics.uma.service.ResourceSetService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
index 641bf96fa..0f4beb0d1 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java
@@ -18,12 +18,15 @@
 /**
  *
  */
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
-import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE_METHOD;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_VERIFIER;
+import static cz.muni.ics.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE;
+import static cz.muni.ics.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE_METHOD;
+import static cz.muni.ics.openid.connect.request.ConnectRequestParameters.CODE_VERIFIER;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.oauth2.repository.OAuth2TokenRepository;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
@@ -34,21 +37,18 @@ import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 
-import org.mitre.data.AbstractPageOperationTemplate;
-import org.mitre.data.DefaultPageCriteria;
-import org.mitre.oauth2.model.AuthenticationHolderEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.model.PKCEAlgorithm;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.repository.AuthenticationHolderRepository;
-import org.mitre.oauth2.repository.OAuth2TokenRepository;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.openid.connect.service.ApprovedSiteService;
+import cz.muni.ics.data.AbstractPageOperationTemplate;
+import cz.muni.ics.data.DefaultPageCriteria;
+import cz.muni.ics.oauth2.model.AuthenticationHolderEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.PKCEAlgorithm;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.repository.AuthenticationHolderRepository;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.openid.connect.service.ApprovedSiteService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -508,7 +508,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.OAuth2TokenEntityService#saveAccessToken(org.mitre.oauth2.model.OAuth2AccessTokenEntity)
+	 * @see cz.muni.ics.oauth2.service.OAuth2TokenEntityService#saveAccessToken(cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -524,7 +524,7 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.OAuth2TokenEntityService#saveRefreshToken(org.mitre.oauth2.model.OAuth2RefreshTokenEntity)
+	 * @see cz.muni.ics.oauth2.service.OAuth2TokenEntityService#saveRefreshToken(cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultSystemScopeService.java
similarity index 85%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultSystemScopeService.java
index 21474fe6e..7b14305ce 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/DefaultSystemScopeService.java
@@ -18,14 +18,14 @@
 /**
  *
  */
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.repository.SystemScopeRepository;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.repository.SystemScopeRepository;
-import org.mitre.oauth2.service.SystemScopeService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -97,7 +97,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
 	};
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.SystemScopeService#getAll()
+	 * @see cz.muni.ics.oauth2.service.SystemScopeService#getAll()
 	 */
 	@Override
 	public Set<SystemScope> getAll() {
@@ -105,7 +105,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.SystemScopeService#getById(java.lang.Long)
+	 * @see cz.muni.ics.oauth2.service.SystemScopeService#getById(java.lang.Long)
 	 */
 	@Override
 	public SystemScope getById(Long id) {
@@ -113,7 +113,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.SystemScopeService#getByValue(java.lang.String)
+	 * @see cz.muni.ics.oauth2.service.SystemScopeService#getByValue(java.lang.String)
 	 */
 	@Override
 	public SystemScope getByValue(String value) {
@@ -121,7 +121,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.SystemScopeService#remove(org.mitre.oauth2.model.SystemScope)
+	 * @see cz.muni.ics.oauth2.service.SystemScopeService#remove(cz.muni.ics.oauth2.model.SystemScope)
 	 */
 	@Override
 	public void remove(SystemScope scope) {
@@ -130,7 +130,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.SystemScopeService#save(org.mitre.oauth2.model.SystemScope)
+	 * @see cz.muni.ics.oauth2.service.SystemScopeService#save(cz.muni.ics.oauth2.model.SystemScope)
 	 */
 	@Override
 	public SystemScope save(SystemScope scope) {
@@ -142,7 +142,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.SystemScopeService#fromStrings(java.util.Set)
+	 * @see cz.muni.ics.oauth2.service.SystemScopeService#fromStrings(java.util.Set)
 	 */
 	@Override
 	public Set<SystemScope> fromStrings(Set<String> scope) {
@@ -154,7 +154,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.SystemScopeService#toStrings(java.util.Set)
+	 * @see cz.muni.ics.oauth2.service.SystemScopeService#toStrings(java.util.Set)
 	 */
 	@Override
 	public Set<String> toStrings(Set<SystemScope> scope) {
@@ -166,7 +166,7 @@ public class DefaultSystemScopeService implements SystemScopeService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.oauth2.service.SystemScopeService#scopesMatch(java.util.Set, java.util.Set)
+	 * @see cz.muni.ics.oauth2.service.SystemScopeService#scopesMatch(java.util.Set, java.util.Set)
 	 */
 	@Override
 	public boolean scopesMatch(Set<String> expected, Set<String> actual) {
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/ServiceUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/ServiceUtils.java
similarity index 87%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/ServiceUtils.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/ServiceUtils.java
index 4940c77e8..c8b047fdb 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/ServiceUtils.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/ServiceUtils.java
@@ -1,7 +1,7 @@
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/UriEncodedClientUserDetailsService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/UriEncodedClientUserDetailsService.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/UriEncodedClientUserDetailsService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/UriEncodedClientUserDetailsService.java
index 8baab7d71..dfd8dcb74 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/UriEncodedClientUserDetailsService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/service/impl/UriEncodedClientUserDetailsService.java
@@ -13,13 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
 import java.io.UnsupportedEncodingException;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/token/ChainedTokenGranter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/ChainedTokenGranter.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/token/ChainedTokenGranter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/ChainedTokenGranter.java
index c53596f26..8b622b70d 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/token/ChainedTokenGranter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/ChainedTokenGranter.java
@@ -18,14 +18,14 @@
 /**
  *
  */
-package org.mitre.oauth2.token;
+package cz.muni.ics.oauth2.token;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/token/DeviceTokenGranter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/DeviceTokenGranter.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/token/DeviceTokenGranter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/DeviceTokenGranter.java
index f7e185e53..63cfc3014 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/token/DeviceTokenGranter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/DeviceTokenGranter.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.token;
+package cz.muni.ics.oauth2.token;
 
 import java.util.Date;
 
-import org.mitre.oauth2.exception.AuthorizationPendingException;
-import org.mitre.oauth2.exception.DeviceCodeExpiredException;
-import org.mitre.oauth2.model.DeviceCode;
-import org.mitre.oauth2.service.DeviceCodeService;
-import org.mitre.oauth2.web.DeviceEndpoint;
+import cz.muni.ics.oauth2.exception.AuthorizationPendingException;
+import cz.muni.ics.oauth2.exception.DeviceCodeExpiredException;
+import cz.muni.ics.oauth2.model.DeviceCode;
+import cz.muni.ics.oauth2.service.DeviceCodeService;
+import cz.muni.ics.oauth2.web.DeviceEndpoint;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
 import org.springframework.security.oauth2.provider.ClientDetails;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/token/JWTAssertionTokenGranter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/JWTAssertionTokenGranter.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/token/JWTAssertionTokenGranter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/JWTAssertionTokenGranter.java
index 02217fc48..f5ae867a2 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/token/JWTAssertionTokenGranter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/JWTAssertionTokenGranter.java
@@ -18,15 +18,15 @@
 /**
  *
  */
-package org.mitre.oauth2.token;
+package cz.muni.ics.oauth2.token;
 
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
 import java.text.ParseException;
 
-import org.mitre.jwt.assertion.AssertionValidator;
-import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
-import org.mitre.openid.connect.assertion.JWTBearerAssertionAuthenticationToken;
+import cz.muni.ics.jwt.assertion.AssertionValidator;
+import cz.muni.ics.oauth2.assertion.AssertionOAuth2RequestFactory;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
+import cz.muni.ics.openid.connect.assertion.JWTBearerAssertionAuthenticationToken;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.security.core.AuthenticationException;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java
index 3896dfd3a..c9ac3dfb6 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java
@@ -18,11 +18,11 @@
 /**
  *
  */
-package org.mitre.oauth2.token;
+package cz.muni.ics.oauth2.token;
 
 import java.util.Set;
 
-import org.mitre.oauth2.service.SystemScopeService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
 import org.springframework.security.oauth2.provider.AuthorizationRequest;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenApiView.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/view/TokenApiView.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenApiView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/view/TokenApiView.java
index cd6eed06c..c49b1d816 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenApiView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/view/TokenApiView.java
@@ -13,8 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.view;
+package cz.muni.ics.oauth2.view;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
 import java.io.IOException;
 import java.io.Writer;
 import java.lang.reflect.Type;
@@ -23,10 +27,6 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/AuthenticationUtilities.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/AuthenticationUtilities.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/AuthenticationUtilities.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/AuthenticationUtilities.java
index ee56889cc..72572cbb2 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/AuthenticationUtilities.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/AuthenticationUtilities.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.web;
+package cz.muni.ics.oauth2.web;
 
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/CorsFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/CorsFilter.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/CorsFilter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/CorsFilter.java
index da2aa69d1..70a8f3003 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/CorsFilter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/CorsFilter.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.oauth2.web;
+package cz.muni.ics.oauth2.web;
 
 import java.io.IOException;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/DeviceEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/DeviceEndpoint.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/DeviceEndpoint.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/DeviceEndpoint.java
index 9c54c9f07..49fe7765d 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/DeviceEndpoint.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/DeviceEndpoint.java
@@ -14,8 +14,18 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.web;
+package cz.muni.ics.oauth2.web;
 
+import cz.muni.ics.oauth2.exception.DeviceCodeCreationException;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.DeviceCode;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.token.DeviceTokenGranter;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Collection;
@@ -24,23 +34,12 @@ import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.UUID;
 
 import javax.servlet.http.HttpSession;
 
 import org.apache.http.client.utils.URIBuilder;
-import org.mitre.oauth2.exception.DeviceCodeCreationException;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.DeviceCode;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.DeviceCodeService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.oauth2.token.DeviceTokenGranter;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.openid.connect.view.JsonErrorView;
+import cz.muni.ics.oauth2.service.DeviceCodeService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -50,7 +49,6 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
 import org.springframework.security.oauth2.common.util.OAuth2Utils;
-import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
 import org.springframework.security.oauth2.provider.AuthorizationRequest;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 import org.springframework.security.oauth2.provider.OAuth2Request;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/IntrospectionEndpoint.java
similarity index 88%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/IntrospectionEndpoint.java
index 7725a5403..4b0be841a 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/IntrospectionEndpoint.java
@@ -15,28 +15,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.web;
-
-import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope;
+package cz.muni.ics.oauth2.web;
 
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.UserInfoService;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.uma.model.ResourceSet;
+import cz.muni.ics.uma.service.ResourceSetService;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.IntrospectionResultAssembler;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.UserInfoService;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.uma.model.ResourceSet;
-import org.mitre.uma.service.ResourceSetService;
+import cz.muni.ics.oauth2.service.IntrospectionResultAssembler;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -98,7 +96,7 @@ public class IntrospectionEndpoint {
 
 		if (auth instanceof OAuth2Authentication) {
 			// the client authenticated with OAuth, do our UMA checks
-			ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
+			AuthenticationUtilities.ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
 
 			// get out the client that was issued the access token (not the token being introspected)
 			OAuth2Authentication o2a = (OAuth2Authentication) auth;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuth2ExceptionHandler.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuth2ExceptionHandler.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuth2ExceptionHandler.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuth2ExceptionHandler.java
index 2a361cffd..75ed7d911 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuth2ExceptionHandler.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuth2ExceptionHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.web;
+package cz.muni.ics.oauth2.web;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuthConfirmationController.java
similarity index 88%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuthConfirmationController.java
index 3d7a7b734..926b6de0c 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/OAuthConfirmationController.java
@@ -18,22 +18,23 @@
 /**
  *
  */
-package org.mitre.oauth2.web;
+package cz.muni.ics.oauth2.web;
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.Sets;
 import com.google.gson.JsonObject;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.request.ConnectRequestParameters;
+import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
+import cz.muni.ics.openid.connect.service.UserInfoService;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
 import org.apache.http.client.utils.URIBuilder;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.ScopeClaimTranslationService;
-import org.mitre.openid.connect.service.UserInfoService;
-import org.mitre.openid.connect.view.HttpCodeView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -43,7 +44,6 @@ import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
 import org.springframework.security.oauth2.provider.AuthorizationRequest;
 import org.springframework.security.oauth2.provider.endpoint.RedirectResolver;
 import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.ModelAttribute;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.SessionAttributes;
 
@@ -55,9 +55,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR;
-
 /**
  * @author jricher
  *
@@ -102,8 +99,8 @@ public class OAuthConfirmationController {
 		AuthorizationRequest authRequest = (AuthorizationRequest) model.get("authorizationRequest");
 		// Check the "prompt" parameter to see if we need to do special processing
 
-		String prompt = (String)authRequest.getExtensions().get(PROMPT);
-		List<String> prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
+		String prompt = (String)authRequest.getExtensions().get(ConnectRequestParameters.PROMPT);
+		List<String> prompts = Splitter.on(ConnectRequestParameters.PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
 		ClientDetailsEntity client = null;
 
 		try {
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/RevocationEndpoint.java
similarity index 91%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/RevocationEndpoint.java
index dd202fe9f..2da5409a9 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/RevocationEndpoint.java
@@ -15,17 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.web;
+package cz.muni.ics.oauth2.web;
 
-import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope;
+import static cz.muni.ics.oauth2.web.AuthenticationUtilities.ensureOAuthScope;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.view.HttpCodeView;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/ScopeAPI.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/ScopeAPI.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/ScopeAPI.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/ScopeAPI.java
index 5aa6d2a3b..0dacad5de 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/ScopeAPI.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/ScopeAPI.java
@@ -18,16 +18,16 @@
 /**
  *
  */
-package org.mitre.oauth2.web;
+package cz.muni.ics.oauth2.web;
 
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
+import cz.muni.ics.openid.connect.web.RootController;
 import java.util.Set;
 
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.openid.connect.view.JsonErrorView;
-import org.mitre.openid.connect.web.RootController;
+import cz.muni.ics.oauth2.service.SystemScopeService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/TokenAPI.java b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/TokenAPI.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/oauth2/web/TokenAPI.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/TokenAPI.java
index 73fa472b4..b1691818d 100644
--- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/TokenAPI.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/TokenAPI.java
@@ -15,23 +15,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.web;
+package cz.muni.ics.oauth2.web;
 
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.view.TokenApiView;
+import cz.muni.ics.openid.connect.service.OIDCTokenService;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
+import cz.muni.ics.openid.connect.web.RootController;
 import java.security.Principal;
 import java.util.List;
 import java.util.Set;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
-import org.mitre.oauth2.view.TokenApiView;
-import org.mitre.openid.connect.service.OIDCTokenService;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.openid.connect.view.JsonErrorView;
-import org.mitre.openid.connect.web.RootController;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/BeanUtil.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/BeanUtil.java
new file mode 100644
index 000000000..b281b21ab
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/BeanUtil.java
@@ -0,0 +1,34 @@
+package cz.muni.ics.oidc;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Service;
+
+/**
+ * Utility class for working with beans.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Service
+public class BeanUtil implements ApplicationContextAware {
+
+	private ApplicationContext context;
+
+	public <T> T getBean(String name, Class<T> beanClass) {
+		return context.getBean(name, beanClass);
+	}
+
+	public <T> T getBean(Class<T> beanClass) {
+		return context.getBean(beanClass);
+	}
+
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		context = applicationContext;
+	}
+
+	public ApplicationContext getContext() {
+		return context;
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/ExecutionTimeLoggingAspect.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/ExecutionTimeLoggingAspect.java
new file mode 100644
index 000000000..1f5dd5213
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/ExecutionTimeLoggingAspect.java
@@ -0,0 +1,21 @@
+package cz.muni.ics.oidc.aop;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class ExecutionTimeLoggingAspect {
+
+    public static final Logger log = LoggerFactory.getLogger(ExecutionTimeLoggingAspect.class);
+
+    @Around("@annotation(LogTimes) && execution(* cz.muni.ics.oidc.server.connectors..* (..))")
+    public Object logExecutionTimeForConnectorsWithParams(ProceedingJoinPoint pjp) throws Throwable {
+        return LoggingUtils.logExecutionTimes(log, pjp);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/LogTimes.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/LogTimes.java
new file mode 100644
index 000000000..83b4c860e
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/LogTimes.java
@@ -0,0 +1,11 @@
+package cz.muni.ics.oidc.aop;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LogTimes {
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/LoggingUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/LoggingUtils.java
new file mode 100644
index 000000000..540871a4d
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/LoggingUtils.java
@@ -0,0 +1,72 @@
+package cz.muni.ics.oidc.aop;
+
+import java.sql.Timestamp;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.slf4j.Logger;
+
+/**
+ * Utility class that takes care of the logging for AOP.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class LoggingUtils {
+
+    /**
+     * Log at TRACE level end of method.
+     * @param log Logger object.
+     * @param jp Join point.
+     * @return Value returned by the methods.
+     */
+    public static Object logExecutionEnd(Logger log, JoinPoint jp, Object result) {
+        String className = jp.getTarget().getClass().getName();
+        String methodName = jp.getSignature().getName();
+        Object[] args = jp.getArgs();
+        log.trace("{}.{}({}) returns: {}", className, methodName, args.length > 0 ? args : "", result);
+        return result;
+    }
+
+    /**
+     * Log at TRACE level end of method.
+     * @param log Logger object.
+     * @param jp Join point.
+     * @throws Throwable thrown exception by the method execution.
+     */
+    public static void logExecutionException(Logger log, JoinPoint jp, Throwable t) throws Throwable {
+        String className = jp.getTarget().getClass().getName();
+        String methodName = jp.getSignature().getName();
+        Object[] args = jp.getArgs();
+        log.warn("{}.{}({}) has thrown {}", className, methodName, args.length > 0 ? args : "", t.getClass(), t);
+        throw t;
+    }
+
+    /**
+     * Log at TRACE level times of start and end of method execution.
+     * @param log Logger object.
+     * @param pjp proceeding join point.
+     * @return Value returned by the methods.
+     * @throws Throwable throw exception by the method execution.
+     */
+    public static Object logExecutionTimes(Logger log, ProceedingJoinPoint pjp) throws Throwable {
+        String className = pjp.getTarget().getClass().getName();
+        String methodName = pjp.getSignature().getName();
+        Object[] args = pjp.getArgs();
+        long start = System.currentTimeMillis();
+
+        log.trace("Execution of {}.{}({}) started at {}",
+                className, methodName, args.length > 0 ? args : "", new Timestamp(start));
+        try {
+            Object result = pjp.proceed();
+            long finish = System.currentTimeMillis();
+            log.trace("Execution of {}.{}({}) finished successfully at {}, execution took {}ms",
+                    className, methodName, args.length > 0 ? args : "", new Timestamp(finish), finish - start);
+            return result;
+        } catch (Throwable e) {
+            long finish = System.currentTimeMillis();
+            log.trace("Execution of {}.{}({}) finished by exception being thrown at {}, execution took {}ms",
+                    className, methodName, args.length > 0 ? args : "", new Timestamp(finish), finish - start);
+            throw e;
+        }
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/MapperLoggingAspect.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/MapperLoggingAspect.java
new file mode 100644
index 000000000..5e66af3b4
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/MapperLoggingAspect.java
@@ -0,0 +1,27 @@
+package cz.muni.ics.oidc.aop;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class MapperLoggingAspect {
+
+    public static final Logger log = LoggerFactory.getLogger(MapperLoggingAspect.class);
+
+    @AfterReturning(value = "execution(* cz.muni.ics.oidc.models.mappers..* (..))", returning = "result")
+    public Object logAroundMethodWithParams(JoinPoint jp, Object result) {
+        return LoggingUtils.logExecutionEnd(log, jp, result);
+    }
+
+    @AfterThrowing(value = "execution(* cz.muni.ics.oidc.models.mappers..* (..))", throwing = "t")
+    public void logAroundMethodWithParams(JoinPoint jp, Throwable t) throws Throwable {
+        LoggingUtils.logExecutionException(log, jp, t);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/ServerLoggingAspect.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/ServerLoggingAspect.java
new file mode 100644
index 000000000..2841dbbab
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/ServerLoggingAspect.java
@@ -0,0 +1,27 @@
+package cz.muni.ics.oidc.aop;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class ServerLoggingAspect {
+
+    public static final Logger log = LoggerFactory.getLogger(ServerLoggingAspect.class);
+
+    @AfterReturning(value = "execution(* cz.muni.ics.oidc.server..* (..))", returning = "result")
+    public Object logAroundMethodWithParams(JoinPoint jp, Object result) {
+        return LoggingUtils.logExecutionEnd(log, jp, result);
+    }
+
+    @AfterThrowing(value = "execution(* cz.muni.ics.oidc.server..* (..))", throwing = "t")
+    public void logAroundMethodWithParams(JoinPoint jp, Throwable t) throws Throwable {
+        LoggingUtils.logExecutionException(log, jp, t);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/WebLoggingAspect.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/WebLoggingAspect.java
new file mode 100644
index 000000000..b021e7611
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/aop/WebLoggingAspect.java
@@ -0,0 +1,27 @@
+package cz.muni.ics.oidc.aop;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class WebLoggingAspect {
+
+    public static final Logger log = LoggerFactory.getLogger(WebLoggingAspect.class);
+
+    @AfterReturning(value = "execution(* cz.muni.ics.oidc.web..* (..))", returning = "result")
+    public Object logAroundMethodWithParams(JoinPoint jp, Object result) {
+        return LoggingUtils.logExecutionEnd(log, jp, result);
+    }
+
+    @AfterThrowing(value = "execution(* cz.muni.ics.oidc.web..* (..))", throwing = "t")
+    public void logAroundMethodWithParams(JoinPoint jp, Throwable t) throws Throwable {
+        LoggingUtils.logExecutionException(log, jp, t);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/ConfigurationException.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/ConfigurationException.java
new file mode 100644
index 000000000..995e4bcfe
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/ConfigurationException.java
@@ -0,0 +1,25 @@
+package cz.muni.ics.oidc.exceptions;
+
+public class ConfigurationException extends Exception {
+
+    public ConfigurationException() {
+        super();
+    }
+
+    public ConfigurationException(String s) {
+        super(s);
+    }
+
+    public ConfigurationException(String s, Throwable throwable) {
+        super(s, throwable);
+    }
+
+    public ConfigurationException(Throwable throwable) {
+        super(throwable);
+    }
+
+    protected ConfigurationException(String s, Throwable throwable, boolean b, boolean b1) {
+        super(s, throwable, b, b1);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/InconvertibleValueException.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/InconvertibleValueException.java
new file mode 100644
index 000000000..25386c38e
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/InconvertibleValueException.java
@@ -0,0 +1,24 @@
+package cz.muni.ics.oidc.exceptions;
+
+public class InconvertibleValueException extends RuntimeException {
+
+	public InconvertibleValueException() {
+		super();
+	}
+
+	public InconvertibleValueException(String s) {
+		super(s);
+	}
+
+	public InconvertibleValueException(String s, Throwable throwable) {
+		super(s, throwable);
+	}
+
+	public InconvertibleValueException(Throwable throwable) {
+		super(throwable);
+	}
+
+	protected InconvertibleValueException(String s, Throwable throwable, boolean b, boolean b1) {
+		super(s, throwable, b, b1);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/MissingFieldException.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/MissingFieldException.java
new file mode 100644
index 000000000..a1e7b340c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/exceptions/MissingFieldException.java
@@ -0,0 +1,23 @@
+package cz.muni.ics.oidc.exceptions;
+
+public class MissingFieldException extends RuntimeException {
+    public MissingFieldException() {
+        super();
+    }
+
+    public MissingFieldException(String s) {
+        super(s);
+    }
+
+    public MissingFieldException(String s, Throwable throwable) {
+        super(s, throwable);
+    }
+
+    public MissingFieldException(Throwable throwable) {
+        super(throwable);
+    }
+
+    protected MissingFieldException(String s, Throwable throwable, boolean b, boolean b1) {
+        super(s, throwable, b, b1);
+    }
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/AttributeMapping.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/AttributeMapping.java
new file mode 100644
index 000000000..632efa9d1
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/AttributeMapping.java
@@ -0,0 +1,141 @@
+package cz.muni.ics.oidc.models;
+
+import com.google.common.base.Strings;
+import cz.muni.ics.oidc.models.enums.PerunAttrValueType;
+import java.util.Objects;
+
+/**
+ * Attribute mapping model. Provides mapping of attribute with an internal name to names specific for interfaces
+ * (i.e. LDAP, RPC, ...)
+ *
+ * Configuration (replace [attrName] with the actual name of the attribute):
+ * <ul>
+ *     <li><b>[attrName].mapping.ldap</b> - name of attribute in LDAP</li>
+ *     <li><b>[attrName].mapping.rpc</b> - name of attribute in LDAP</li>
+ *     <li><b>[attrName].mapping.type</b> - [STRING|INTEGER|BOOLEAN|ARRAY|MAP_JSON|MAP_KEY_VALUE]
+ *     - type of attribute value, defaults to STRING</li>
+ *     <li><b>[attrName].mapping.separator</b> - separator of keys ands values if type equals to MAP_KEY_VALUE, defaults to '='</li>
+ * </ul>
+ * @see cz.muni.ics.oidc.server.AttributeMappingsService for attrName configurations
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class AttributeMapping {
+
+	private String identifier;
+	private String rpcName;
+	private String ldapName;
+	private PerunAttrValueType attrType;
+	private String separator;
+
+	public AttributeMapping() {
+	}
+
+	public AttributeMapping(String identifier, String rpcName, String ldapName, String type) {
+		super();
+		this.setIdentifier(identifier);
+		this.setRpcName(rpcName);
+		this.setLdapName(ldapName);
+		this.setAttrType(type);
+		this.setSeparator("");
+	}
+
+	public AttributeMapping(String identifier, String rpcName, String ldapName, String type, String separator) {
+		super();
+		this.setIdentifier(identifier);
+		this.setRpcName(rpcName);
+		this.setLdapName(ldapName);
+		this.setAttrType(type);
+		this.setSeparator(separator);
+	}
+
+	public String getIdentifier() {
+		return identifier;
+	}
+
+	public void setIdentifier(String identifier) {
+		if (Strings.isNullOrEmpty(identifier)) {
+			throw new IllegalArgumentException("identifier cannot be null nor empty");
+		}
+
+		this.identifier = identifier;
+	}
+
+	public String getRpcName() {
+		return rpcName;
+	}
+
+	public void setRpcName(String rpcName) {
+		if (Strings.isNullOrEmpty(rpcName)) {
+			throw new IllegalArgumentException("rpcName cannot be null nor empty");
+		}
+
+		this.rpcName = rpcName;
+	}
+
+	public String getLdapName() {
+		return ldapName;
+	}
+
+	public void setLdapName(String ldapName) {
+		this.ldapName = ldapName;
+	}
+
+	public PerunAttrValueType getAttrType() {
+		return attrType;
+	}
+
+	public void setAttrType(String typeStr) {
+		PerunAttrValueType type = PerunAttrValueType.parse(typeStr);
+		this.setAttrType(type);
+	}
+
+	public void setAttrType(PerunAttrValueType attrType) {
+		if (attrType == null) {
+			throw new IllegalArgumentException("Type cannot be null");
+		}
+		this.attrType = attrType;
+	}
+
+	public String getSeparator() {
+		return this.separator;
+	}
+
+	public void setSeparator(String separator) {
+		if (separator == null || separator.trim().isEmpty()) {
+			separator = "=";
+		}
+		this.separator = separator;
+	}
+
+	@Override
+	public String toString() {
+		String str = "AttributeMapping{" +
+				"identifier='" + identifier + '\'' +
+				", rpcName='" + rpcName + '\'' +
+				", ldapName='" + ldapName + '\'' +
+				", attrType='" + attrType;
+		if (PerunAttrValueType.MAP_KEY_VALUE.equals(attrType)) {
+			str += "', separator='" + separator;
+		}
+		str += "'}";
+
+		return str;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		AttributeMapping that = (AttributeMapping) o;
+		return Objects.equals(identifier, that.identifier) &&
+				Objects.equals(rpcName, that.rpcName) &&
+				Objects.equals(ldapName, that.ldapName);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(identifier, rpcName, ldapName);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Aup.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Aup.java
new file mode 100644
index 000000000..4b8f7deee
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Aup.java
@@ -0,0 +1,127 @@
+package cz.muni.ics.oidc.models;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Strings;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+
+/**
+ * AUP object model.
+ *
+ * @author Dominik Baranek <baranek@ics.muni.cz>
+ */
+public class Aup {
+
+    public static final String SIGNED_ON = "signed_on";
+
+    private static final DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+    private String version;
+    private String date;
+    private String link;
+    private String text;
+
+    @JsonProperty(SIGNED_ON)
+    private String signedOn = null;
+
+    public Aup() {
+    }
+
+    public Aup(String version, String date, String link, String text, String signedOn) {
+        this.setVersion(version);
+        this.setDate(date);
+        this.setLink(link);
+        this.setText(text);
+        this.setSignedOn(signedOn);
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        if (Strings.isNullOrEmpty(version)) {
+            throw new IllegalArgumentException("version cannot be null or empty");
+        }
+
+        this.version = version;
+    }
+
+    public String getDate() {
+        return date;
+    }
+
+    @JsonIgnore
+    public LocalDate getDateAsLocalDate() {
+        return LocalDate.parse(date, format);
+    }
+
+    public void setDate(String date) {
+        if (Strings.isNullOrEmpty(date)) {
+            throw new IllegalArgumentException("date cannot be null or empty");
+        }
+
+        this.date = date;
+    }
+
+    public String getLink() {
+        return link;
+    }
+
+    public void setLink(String link) {
+        if (Strings.isNullOrEmpty(link)) {
+            throw new IllegalArgumentException("link cannot be null or empty");
+        }
+
+        this.link = link;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        if (text == null) {
+            throw new IllegalArgumentException("version cannot be null");
+        }
+
+        this.text = text;
+    }
+
+    public String getSignedOn() {
+        return signedOn;
+    }
+
+    public void setSignedOn(String signedOn) {
+        this.signedOn = signedOn;
+    }
+
+    @Override
+    public String toString() {
+        return "Aup{" +
+                "version='" + version + '\'' +
+                ", date='" + date + '\'' +
+                ", link='" + link + '\'' +
+                ", text='" + text + '\'' +
+                ", signedOn='" + signedOn + '\'' +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Aup aup = (Aup) o;
+        return Objects.equals(version, aup.version) &&
+                Objects.equals(date, aup.date) &&
+                Objects.equals(link, aup.link) &&
+                Objects.equals(text, aup.text);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(version, date, link, text);
+    }
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/ExtSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/ExtSource.java
new file mode 100644
index 000000000..1b7fe7bba
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/ExtSource.java
@@ -0,0 +1,72 @@
+package cz.muni.ics.oidc.models;
+
+import com.google.common.base.Strings;
+import java.util.Objects;
+
+/**
+ * Model for ExtSource
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class ExtSource extends Model {
+
+	private String name;
+	private String type;
+
+	public ExtSource() {
+	}
+
+	public ExtSource(Long id, String name, String type) {
+		super(id);
+		this.setName(name);
+		this.setType(type);
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		if (Strings.isNullOrEmpty(name)) {
+			throw new IllegalArgumentException("name cannot be null nor empty");
+		}
+
+		this.name = name;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		if (Strings.isNullOrEmpty(type)) {
+			throw new IllegalArgumentException("type cannot be null nor empty");
+		}
+
+		this.type = type;
+	}
+
+	@Override
+	public String toString() {
+		return "ExtSource{" +
+				"id=" + getId() +
+				", name='" + name + '\'' +
+				", type='" + type + '\'' +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		ExtSource extSource = (ExtSource) o;
+		return Objects.equals(name, extSource.name) &&
+				Objects.equals(type, extSource.type);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), name, type);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Facility.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Facility.java
new file mode 100644
index 000000000..39bc6a8c6
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Facility.java
@@ -0,0 +1,68 @@
+package cz.muni.ics.oidc.models;
+
+import com.google.common.base.Strings;
+import java.util.Objects;
+
+/**
+ * Facility object model.
+ *
+ * @author Peter Jancus <jancus@ics.muni.cz>
+ */
+public class Facility extends Model {
+
+	private String name;
+	private String description;
+
+	public Facility() {
+	}
+
+	public Facility(Long id, String name, String description) {
+		super(id);
+		this.name = name;
+		this.description = description;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		if (Strings.isNullOrEmpty(name)) {
+			throw new IllegalArgumentException("name cannot be null nor empty");
+		}
+
+		this.name = name;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	@Override
+	public String toString() {
+		return "Facility{" +
+				"id=" + getId() +
+				", name='" + name + '\'' +
+				", description='" + description + '\'' +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		Facility facility = (Facility) o;
+		return Objects.equals(name, facility.name) &&
+				Objects.equals(description, facility.description);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), name, description);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Group.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Group.java
new file mode 100644
index 000000000..a70a7b630
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Group.java
@@ -0,0 +1,169 @@
+package cz.muni.ics.oidc.models;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.base.Strings;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Group object model.
+ *
+ * @author Peter Jancus <jancus@ics.muni.cz>
+ */
+public class Group extends Model {
+
+	private Long parentGroupId;
+	private String name;
+	private String description;
+	private String uniqueGroupName; // voShortName + ":" + group name
+	private String uuid;
+	private Long voId;
+
+	private Map<String, JsonNode> attributes = new LinkedHashMap<>();
+
+	public Group() {
+	}
+
+	public Group(Long id, Long parentGroupId, String name, String description, String uniqueGroupName, String uuid, Long voId) {
+		super(id);
+		this.setParentGroupId(parentGroupId);
+		this.setName(name);
+		this.setDescription(description);
+		this.setUniqueGroupName(uniqueGroupName);
+		this.setUuid(uuid);
+		this.setVoId(voId);
+	}
+
+	public Long getParentGroupId() {
+		return parentGroupId;
+	}
+
+	public void setParentGroupId(Long parentGroupId) {
+		this.parentGroupId = parentGroupId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		if (Strings.isNullOrEmpty(name)) {
+			throw new IllegalArgumentException("name cannot be null nor empty");
+		}
+
+		this.name = name;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		if (Strings.isNullOrEmpty(description)) {
+			throw new IllegalArgumentException("description cannot be null nor empty");
+		}
+
+		this.description = description;
+	}
+
+	public void setUniqueGroupName(String uniqueGroupName) {
+		this.uniqueGroupName = uniqueGroupName;
+	}
+
+	public String getUuid() {
+		return uuid;
+	}
+
+	public void setUuid(String uuid) {
+		this.uuid = uuid;
+	}
+
+	public Long getVoId() {
+		return voId;
+	}
+
+	public void setVoId(Long voId) {
+		if (voId == null) {
+			throw new IllegalArgumentException("voId cannot be null");
+		}
+
+		this.voId = voId;
+	}
+
+	public Map<String, JsonNode> getAttributes() {
+		return attributes;
+	}
+
+	public void setAttributes(Map<String, JsonNode> attributes) {
+		this.attributes = attributes;
+	}
+
+	/**
+	 * Gets identifier voShortName:group.name usable for groupNames in AARC format.
+	 */
+	public String getUniqueGroupName() {
+		return uniqueGroupName;
+	}
+
+	/**
+	 * Gets attribute by urn name
+	 *
+	 * @param attributeName urn name of attribute
+	 * @return attribute
+	 */
+	public JsonNode getAttributeByUrnName(String attributeName) {
+		if (attributes == null || !attributes.containsKey(attributeName)) {
+			return null;
+		}
+
+		return attributes.get(attributeName);
+	}
+
+	/**
+	 * Gets attribute by friendly name
+	 *
+	 * @param attributeName attribute name
+	 * @param attributeUrnPrefix urn prefix of attribute
+	 * @return attribute
+	 */
+	public JsonNode getAttributeByFriendlyName(String attributeName, String attributeUrnPrefix) {
+		String key = attributeUrnPrefix + ":" + attributeName;
+
+		if (attributes == null || !attributes.containsKey(key)) {
+			return null;
+		}
+
+		return attributes.get(key);
+	}
+
+	@Override
+	public String toString() {
+		return "Group{" +
+				"id=" + getId() +
+				", name='" + name + '\'' +
+				", uniqueGroupName='" + uniqueGroupName + '\'' +
+				", description='" + description + '\'' +
+				", parentGroupId=" + parentGroupId +
+				", voId=" + voId +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		Group group = (Group) o;
+		return Objects.equals(parentGroupId, group.parentGroupId) &&
+				Objects.equals(name, group.name) &&
+				Objects.equals(description, group.description) &&
+				Objects.equals(uniqueGroupName, group.uniqueGroupName) &&
+				Objects.equals(voId, group.voId);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), parentGroupId, name, description, uniqueGroupName, voId);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Member.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Member.java
new file mode 100644
index 000000000..f928bde6c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Member.java
@@ -0,0 +1,88 @@
+package cz.muni.ics.oidc.models;
+
+import cz.muni.ics.oidc.models.enums.MemberStatus;
+import java.util.Objects;
+
+/**
+ * Member object model.
+ *
+ * @author Peter Jancus <jancus@ics.muni.cz>
+ */
+public class Member extends Model {
+
+	private Long userId;
+	private Long voId;
+	private MemberStatus status;
+
+	public Member() {
+	}
+
+	public Member(Long id, Long userId, Long voId, MemberStatus status) {
+		super(id);
+		this.setUserId(userId);
+		this.setVoId(voId);
+		this.setStatus(status);
+	}
+
+	public Long getUserId() {
+		return userId;
+	}
+
+	public void setUserId(Long userId) {
+		if (userId == null) {
+			throw new IllegalArgumentException("userId cannot be null");
+		}
+
+		this.userId = userId;
+	}
+
+	public Long getVoId() {
+		return voId;
+	}
+
+	public void setVoId(Long voId) {
+		if (voId == null) {
+			throw new IllegalArgumentException("voId cannot be null");
+		}
+
+		this.voId = voId;
+	}
+
+	public MemberStatus getStatus() {
+		return status;
+	}
+
+	public void setStatus(MemberStatus status) {
+		if (status == null) {
+			throw new IllegalArgumentException("status cannot be null nor empty");
+		}
+
+		this.status = status;
+	}
+
+	@Override
+	public String toString() {
+		return "Member{" +
+				"id=" + getId() +
+				", userId=" + userId +
+				", voId=" + voId +
+				", status='" + status + '\'' +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		Member member = (Member) o;
+		return Objects.equals(userId, member.userId) &&
+				Objects.equals(voId, member.voId) &&
+				Objects.equals(status, member.status);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), userId, voId, status);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Model.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Model.java
new file mode 100644
index 000000000..7dabd3902
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Model.java
@@ -0,0 +1,46 @@
+package cz.muni.ics.oidc.models;
+
+/**
+ * Basic model with ID. Should be extended by another specific models with specific variables.
+ *
+ * @author Peter Jancus <jancus@ics.muni.cz>
+ */
+public abstract class Model {
+
+	private Long id;
+
+	public Model() {
+	}
+
+	public Model(Long id) {
+		super();
+		this.setId(id);
+	}
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		if (id == null) {
+			throw new IllegalArgumentException("id cannot be null");
+		}
+
+		this.id = id;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (!(o instanceof Model)) return false;
+
+		Model model = (Model) o;
+
+		return getId() != null ? getId().equals(model.getId()) : model.getId() == null;
+	}
+
+	@Override
+	public int hashCode() {
+		return getId() != null ? getId().hashCode() : 0;
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttribute.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttribute.java
new file mode 100644
index 000000000..4e42f9479
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttribute.java
@@ -0,0 +1,194 @@
+package cz.muni.ics.oidc.models;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.springframework.util.StringUtils;
+
+/**
+ * Perun Attribute model
+ *
+ * @author Dominik Frantisek Bucik <bucik@.ics.muni.cz>
+ * @author Ondrej Ernst <ondra.ernst@gmail.com>
+ */
+public class PerunAttribute extends PerunAttributeValueAwareModel {
+
+    private static final String BEAN_NAME = "Attribute";
+
+    private Long id;
+    private String friendlyName;
+    private String namespace;
+    private String description;
+    private String displayName;
+    private boolean writable;
+    private boolean unique;
+    private String entity;
+    private String baseFriendlyName;
+    private String friendlyNameParameter;
+    private String valueCreatedAt;
+    private String valueModifiedAt;
+
+    public PerunAttribute(Long id, String friendlyName, String namespace, String description, String type,
+                          String displayName, boolean writable, boolean unique, String entity, String baseFriendlyName,
+                          String friendlyNameParameter, JsonNode value, String valueCreatedAt, String valueModifiedAt)
+    {
+        super(type, value);
+        this.setId(id);
+        this.setFriendlyName(friendlyName);
+        this.setNamespace(namespace);
+        this.setDescription(description);
+        this.setType(type);
+        this.setDisplayName(displayName);
+        this.setWritable(writable);
+        this.setUnique(unique);
+        this.setEntity(entity);
+        this.setBaseFriendlyName(baseFriendlyName);
+        this.setFriendlyNameParameter(friendlyNameParameter);
+        this.setValue(type, value);
+        this.setValueCreatedAt(valueCreatedAt);
+        this.setValueModifiedAt(valueModifiedAt);
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getFriendlyName() {
+        return friendlyName;
+    }
+
+    public void setFriendlyName(String friendlyName) {
+        if (!StringUtils.hasText(friendlyName)) {
+            throw new IllegalArgumentException("friendlyName cannot be empty");
+        }
+
+        this.friendlyName = friendlyName;
+    }
+
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public void setNamespace(String namespace) {
+        if (!StringUtils.hasText(namespace)) {
+            throw new IllegalArgumentException("namespace cannot be empty");
+        }
+
+        this.namespace = namespace;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        if (!StringUtils.hasText(displayName)) {
+            throw new IllegalArgumentException("displayName cannot be empty");
+        }
+
+        this.displayName = displayName;
+    }
+
+    public boolean isWritable() {
+        return writable;
+    }
+
+    public void setWritable(boolean writable) {
+        this.writable = writable;
+    }
+
+    public boolean isUnique() {
+        return unique;
+    }
+
+    public void setUnique(boolean unique) {
+        this.unique = unique;
+    }
+
+    public String getEntity() {
+        return entity;
+    }
+
+    public void setEntity(String entity) {
+        if (!StringUtils.hasText(entity)) {
+            throw new IllegalArgumentException("entity cannot be empty");
+        }
+
+        this.entity = entity;
+    }
+
+    public String getBaseFriendlyName() {
+        return baseFriendlyName;
+    }
+
+    public void setBaseFriendlyName(String baseFriendlyName) {
+        this.baseFriendlyName = baseFriendlyName;
+    }
+
+    public String getFriendlyNameParameter() {
+        return friendlyNameParameter;
+    }
+
+    public void setFriendlyNameParameter(String friendlyNameParameter) {
+        this.friendlyNameParameter = friendlyNameParameter;
+    }
+
+    public String getValueCreatedAt() {
+        return valueCreatedAt;
+    }
+
+    public void setValueCreatedAt(String valueCreatedAt) {
+        this.valueCreatedAt = valueCreatedAt;
+    }
+
+    public String getValueModifiedAt() {
+        return valueModifiedAt;
+    }
+
+    public void setValueModifiedAt(String valueModifiedAt) {
+        this.valueModifiedAt = valueModifiedAt;
+    }
+
+    @JsonIgnore
+    public String getUrn() {
+        return this.namespace + ':' + this.friendlyName;
+    }
+
+    public ObjectNode toJson() {
+        ObjectNode node = JsonNodeFactory.instance.objectNode();
+
+        node.put("id", id);
+        node.put("friendlyName", friendlyName);
+        node.put("namespace", namespace);
+        node.put("type", super.getType());
+        node.put("displayName", displayName);
+        node.put("writable", writable);
+        node.put("unique", unique);
+        node.put("entity", entity);
+        node.put("beanName", BEAN_NAME);
+        node.put("baseFriendlyName", baseFriendlyName);
+        node.put("friendlyName", friendlyName);
+        node.put("friendlyNameParameter", friendlyNameParameter);
+        node.set("value", this.valueAsJson());
+
+        return node;
+    }
+
+    public PerunAttributeValue toPerunAttributeValue() {
+        return new PerunAttributeValue(this.getUrn(), super.getType(), this.valueAsJson());
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttributeValue.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttributeValue.java
new file mode 100644
index 000000000..46af02058
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttributeValue.java
@@ -0,0 +1,48 @@
+package cz.muni.ics.oidc.models;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.Objects;
+
+/**
+ * Model representing value of attribute from Perun.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunAttributeValue extends PerunAttributeValueAwareModel {
+
+    private String attrName;
+
+    public PerunAttributeValue(String attrName, String type, JsonNode value) {
+        super(type, value);
+        this.setAttrName(attrName);
+    }
+
+    public String getAttrName() {
+        return attrName;
+    }
+
+    public void setAttrName(String attrName) {
+        this.attrName = attrName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        PerunAttributeValue that = (PerunAttributeValue) o;
+        return super.equals(that) && Objects.equals(attrName, that.attrName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getType(), valueAsJson(), attrName);
+    }
+
+    @Override
+    public String toString() {
+        return "PerunAttributeValue{" +
+                "attrName='" + attrName + '\'' +
+                '}';
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttributeValueAwareModel.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttributeValueAwareModel.java
new file mode 100644
index 000000000..73f6eef0a
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunAttributeValueAwareModel.java
@@ -0,0 +1,164 @@
+package cz.muni.ics.oidc.models;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import cz.muni.ics.oidc.exceptions.InconvertibleValueException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.springframework.util.StringUtils;
+
+/**
+ * Interface should be implemented by all models that represent value of an Attribute from Perun.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public abstract class PerunAttributeValueAwareModel {
+
+    public final static String STRING_TYPE = "java.lang.String";
+    public final static String INTEGER_TYPE = "java.lang.Integer";
+    public final static String BOOLEAN_TYPE = "java.lang.Boolean";
+    public final static String ARRAY_TYPE = "java.util.ArrayList";
+    public final static String MAP_TYPE = "java.util.LinkedHashMap";
+    public final static String LARGE_STRING_TYPE = "java.lang.LargeString";
+    public final static String LARGE_ARRAY_LIST_TYPE = "java.util.LargeArrayList";
+
+    private String type;
+    private JsonNode value;
+
+    public PerunAttributeValueAwareModel(String type, JsonNode value) {
+        this.setType(type);
+        this.setValue(type, value);
+    }
+
+    public void setType(String type) {
+        if (!StringUtils.hasText(type)) {
+            throw new IllegalArgumentException("type can't be null or empty");
+        }
+
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setValue(String type, JsonNode value) {
+        if (PerunAttributeValueAwareModel.isNullValue(value)) {
+            if (BOOLEAN_TYPE.equals(type)) {
+                value = JsonNodeFactory.instance.booleanNode(false);
+            } else if (ARRAY_TYPE.equals(type)) {
+                value = JsonNodeFactory.instance.arrayNode();
+            } else if (MAP_TYPE.equals(type)) {
+                value = JsonNodeFactory.instance.objectNode();
+            } else {
+                value = JsonNodeFactory.instance.nullNode();
+            }
+        }
+
+        this.value = value;
+    }
+
+    public String valueAsString() {
+        if ((STRING_TYPE.equals(type) || LARGE_STRING_TYPE.equals(type))) {
+            if (PerunAttributeValueAwareModel.isNullValue(value)) {
+                return null;
+            } else if (value instanceof TextNode) {
+                return value.textValue();
+            }
+        }
+
+        return value.asText();
+    }
+
+    public Integer valueAsInteger() {
+        if (INTEGER_TYPE.equals(type)) {
+            if (PerunAttributeValueAwareModel.isNullValue(value)) {
+                return null;
+            } else if (value instanceof NumericNode) {
+                return value.intValue();
+            }
+        }
+
+        throw this.inconvertible(Long.class.getName());
+    }
+
+    public boolean valueAsBoolean() {
+        if (BOOLEAN_TYPE.equals(type)) {
+            if (value == null || value instanceof NullNode) {
+                return false;
+            } else if (value instanceof BooleanNode) {
+                return value.asBoolean();
+            }
+        }
+
+        throw this.inconvertible(Boolean.class.getName());
+    }
+
+    public List<String> valueAsList() {
+        List<String> arr = new ArrayList<>();
+        if ((ARRAY_TYPE.equals(type) || LARGE_ARRAY_LIST_TYPE.equals(type))) {
+            if (PerunAttributeValueAwareModel.isNullValue(value)) {
+                return null;
+            } else if (value instanceof ArrayNode) {
+                ArrayNode arrJson = (ArrayNode) value;
+                arrJson.forEach(item -> arr.add(item.asText()));
+            }
+        } else {
+            arr.add(this.valueAsString());
+        }
+
+        return arr;
+    }
+
+    public Map<String, String> valueAsMap() {
+        if (MAP_TYPE.equals(type)) {
+            if (PerunAttributeValueAwareModel.isNullValue(value)) {
+                return new HashMap<>();
+            } else if (value instanceof ObjectNode) {
+                Map<String, String> res = new HashMap<>();
+                ObjectNode objJson = (ObjectNode) value;
+                Iterator<String> it = objJson.fieldNames();
+                while (it.hasNext()) {
+                    String key = it.next();
+                    res.put(key, objJson.get(key).asText());
+                }
+                return res;
+            }
+        }
+
+        throw this.inconvertible(Map.class.getName());
+    }
+
+    public JsonNode valueAsJson() {
+        return this.value;
+    }
+
+    public boolean isNullValue() {
+        return value == null ||
+                value instanceof NullNode ||
+                value.isNull() ||
+                "null".equalsIgnoreCase(value.asText());
+    }
+
+    public static boolean isNullValue(JsonNode value) {
+        return value == null ||
+                value instanceof NullNode ||
+                value.isNull() ||
+                "null".equalsIgnoreCase(value.asText());
+    }
+
+    private InconvertibleValueException inconvertible(String clazzName) {
+        return new InconvertibleValueException("Cannot convert value of attribute to " + clazzName +
+                " for object: " + this.toString());
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunUser.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunUser.java
new file mode 100644
index 000000000..ffdd343d4
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/PerunUser.java
@@ -0,0 +1,71 @@
+package cz.muni.ics.oidc.models;
+
+import com.google.common.base.Strings;
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * Represents user from Perun.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunUser extends Model implements Serializable {
+
+	private String firstName;
+	private String lastName;
+
+	public PerunUser() {
+	}
+
+	public PerunUser(long id, String firstName, String lastName) {
+		super(id);
+		this.setFirstName(firstName);
+		this.setLastName(lastName);
+	}
+
+	public String getFirstName() {
+		return firstName;
+	}
+
+	public void setFirstName(String firstName) {
+		this.firstName = firstName;
+	}
+
+	public String getLastName() {
+		return lastName;
+	}
+
+	public void setLastName(String lastName) {
+		if (Strings.isNullOrEmpty(lastName)) {
+			throw new IllegalArgumentException("name can't be null or empty");
+		}
+
+		this.lastName = lastName;
+	}
+
+	@Override
+	public String toString() {
+		return "PerunUser{" +
+				"id=" + getId() +
+				", firstName='" + firstName + '\'' +
+				", lastName='" + lastName + '\'' +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		PerunUser user = (PerunUser) o;
+		return Objects.equals(firstName, user.firstName) &&
+				Objects.equals(lastName, user.lastName);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), firstName, lastName);
+	}
+
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Resource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Resource.java
new file mode 100644
index 000000000..b521e22cf
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Resource.java
@@ -0,0 +1,121 @@
+package cz.muni.ics.oidc.models;
+
+import com.google.common.base.Strings;
+import java.util.Objects;
+
+/**
+ * Resource object model.
+ *
+ * @author Peter Jancus <jancus@ics.muni.cz>
+ */
+public class Resource extends Model {
+
+	private Long voId;
+	private Long facilityId;
+	private String name;
+	private String description;
+	private Vo vo;
+
+	public Resource() {	}
+
+	public Resource(Long id, Long facilityId, Long voId, String name, String description) {
+		super(id);
+		this.setVoId(voId);
+		this.setFacilityId(facilityId);
+		this.setName(name);
+		this.setDescription(description);
+	}
+
+	/**
+	 * Should be used when RichResource is obtained from Perun
+	 */
+	public Resource(Long id, Long facilityId, Long voId, String name, String description, Vo vo) {
+		this(id, facilityId, voId, name, description);
+		this.setVo(vo);
+	}
+
+	public Long getFacilityId() {
+		return facilityId;
+	}
+
+	public void setFacilityId(Long facilityId) {
+		if (facilityId == null) {
+			throw new IllegalArgumentException("facilityId can't be null");
+		}
+
+		this.facilityId = facilityId;
+	}
+
+	public Long getVoId() {
+		return voId;
+	}
+
+	public void setVoId(Long voId) {
+		if (voId == null) {
+			throw new IllegalArgumentException("voId can't be null");
+		}
+
+		this.voId = voId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		if (Strings.isNullOrEmpty(name)) {
+			throw new IllegalArgumentException("name can't be null or empty");
+		}
+
+		this.name = name;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		if (description == null) {
+			throw new IllegalArgumentException("description can't be null");
+		}
+
+		this.description = description;
+	}
+
+	public Vo getVo() {
+		return vo;
+	}
+
+	public void setVo(Vo vo) {
+		this.vo = vo;
+	}
+
+	@Override
+	public String toString() {
+		return "Resource{" +
+				"id=" + getId() +
+				", voId=" + voId +
+				", name='" + name + '\'' +
+				", description='" + description + '\'' +
+				", vo=" + vo +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		Resource resource = (Resource) o;
+		return Objects.equals(voId, resource.voId) &&
+				Objects.equals(name, resource.name) &&
+				Objects.equals(description, resource.description) &&
+				Objects.equals(vo, resource.vo);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), voId, name, description, vo);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/UserExtSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/UserExtSource.java
new file mode 100644
index 000000000..40448f8ba
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/UserExtSource.java
@@ -0,0 +1,113 @@
+package cz.muni.ics.oidc.models;
+
+import com.google.common.base.Strings;
+import java.sql.Timestamp;
+import java.util.Objects;
+
+/**
+ * Model of Perun UserExtSource
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class UserExtSource extends Model {
+
+	private ExtSource extSource;
+	private String login;
+	private int loa = 0;
+	private boolean persistent;
+	private Timestamp lastAccess;
+
+	public UserExtSource() {
+	}
+
+	public UserExtSource(Long id, ExtSource extSource, String login, int loa, boolean persistent, Timestamp lastAccess) {
+		super(id);
+		this.setExtSource(extSource);
+		this.setLogin(login);
+		this.setLoa(loa);
+		this.setPersistent(persistent);
+		this.setLastAccess(lastAccess);
+	}
+
+	public ExtSource getExtSource() {
+		return extSource;
+	}
+
+	public void setExtSource(ExtSource extSource) {
+		if (extSource == null) {
+			throw new IllegalArgumentException("extSource can't be null");
+		}
+
+		this.extSource = extSource;
+	}
+
+	public String getLogin() {
+		return login;
+	}
+
+	public void setLogin(String login) {
+		if (Strings.isNullOrEmpty(login)) {
+			throw new IllegalArgumentException("login can't be null or empty");
+		}
+
+		this.login = login;
+	}
+
+	public int getLoa() {
+		return loa;
+	}
+
+	public void setLoa(int loa) {
+		if (loa < 0) {
+			throw new IllegalArgumentException("loa has to be 0 or higher");
+		}
+
+		this.loa = loa;
+	}
+
+	public boolean isPersistent() {
+		return persistent;
+	}
+
+	public void setPersistent(boolean persistent) {
+		this.persistent = persistent;
+	}
+
+	public Timestamp getLastAccess() {
+		return lastAccess;
+	}
+
+	public void setLastAccess(Timestamp lastAccess) {
+		this.lastAccess = lastAccess;
+	}
+
+	@Override
+	public String toString() {
+		return "UserExtSource{" +
+				"id=" + getId() +
+				", extSource='" + extSource.toString() + '\'' +
+				", login='" + login + '\'' +
+				", loa=" + loa +
+				", persistent=" + persistent +
+				", lastAccess=" + lastAccess +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		UserExtSource that = (UserExtSource) o;
+		return loa == that.loa &&
+				persistent == that.persistent &&
+				Objects.equals(extSource, that.extSource) &&
+				Objects.equals(login, that.login) &&
+				Objects.equals(lastAccess, that.lastAccess);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), extSource, login, loa, persistent, lastAccess);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Vo.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Vo.java
new file mode 100644
index 000000000..2e6f9ce52
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/Vo.java
@@ -0,0 +1,71 @@
+package cz.muni.ics.oidc.models;
+
+import com.google.common.base.Strings;
+import java.util.Objects;
+
+/**
+ * Virtual Organization (Vo) object model.
+ *
+ * @author Dominik Frantisek Bucik <bucik@.ics.muni.cz>
+ */
+public class Vo extends Model {
+
+	private String name;
+	private String shortName;
+
+	public Vo() { }
+
+	public Vo(Long id, String name, String shortName) {
+		super(id);
+		this.setName(name);
+		this.setShortName(shortName);
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		if (Strings.isNullOrEmpty(name)) {
+			throw new IllegalArgumentException("name can't be null or empty");
+		}
+
+		this.name = name;
+	}
+
+	public String getShortName() {
+		return shortName;
+	}
+
+	public void setShortName(String shortName) {
+		if (Strings.isNullOrEmpty(shortName)) {
+			throw new IllegalArgumentException("shortName can't be null or empty");
+		}
+
+		this.shortName = shortName;
+	}
+
+	@Override
+	public String toString() {
+		return "Vo{" +
+				"id=" + this.getId() +
+				", name='" + name + '\'' +
+				", shortName='" + shortName + '\'' +
+				'}';
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) return true;
+		if (o == null || getClass() != o.getClass()) return false;
+		if (!super.equals(o)) return false;
+		Vo vo = (Vo) o;
+		return Objects.equals(name, vo.name) &&
+				Objects.equals(shortName, vo.shortName);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), name, shortName);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/MemberStatus.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/MemberStatus.java
new file mode 100644
index 000000000..8c7a608b0
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/MemberStatus.java
@@ -0,0 +1,36 @@
+package cz.muni.ics.oidc.models.enums;
+
+/**
+ * Enum represents status of member in GROUP / VO.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public enum MemberStatus {
+
+	VALID,
+	EXPIRED,
+	INVALID,
+	DISABLED,
+	SUSPENDED;
+
+	public static MemberStatus fromString(String status) {
+		if (status == null) {
+			return null;
+		}
+
+		switch(status.toUpperCase().trim()) {
+			case "VALID":
+				return VALID;
+			case "INVALID":
+				return INVALID;
+			case "EXPIRED":
+				return EXPIRED;
+			case "DISABLED":
+				return DISABLED;
+			case "SUSPENDED":
+				return SUSPENDED;
+			default:
+				return null;
+		}
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/PerunAttrValueType.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/PerunAttrValueType.java
new file mode 100644
index 000000000..f2cd4da9d
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/PerunAttrValueType.java
@@ -0,0 +1,28 @@
+package cz.muni.ics.oidc.models.enums;
+
+public enum PerunAttrValueType {
+
+	STRING,
+	INTEGER,
+	BOOLEAN,
+	ARRAY,
+	MAP_JSON,
+	MAP_KEY_VALUE;
+
+	public static PerunAttrValueType parse(String str){
+		if (str == null) {
+			return STRING;
+		}
+
+		switch (str.toLowerCase()) {
+			case "integer": return INTEGER;
+			case "boolean": return BOOLEAN;
+			case "array":
+			case "list": return ARRAY;
+			case "map_json": return MAP_JSON;
+			case "map_key_value": return MAP_KEY_VALUE;
+			default: return STRING;
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/PerunEntityType.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/PerunEntityType.java
new file mode 100644
index 000000000..f5fc86c6d
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/enums/PerunEntityType.java
@@ -0,0 +1,11 @@
+package cz.muni.ics.oidc.models.enums;
+
+public enum PerunEntityType {
+
+	USER,
+	GROUP,
+	VO,
+	FACILITY,
+	RESOURCE
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/exceptions/InconvertibleValueException.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/exceptions/InconvertibleValueException.java
new file mode 100644
index 000000000..89f69b450
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/exceptions/InconvertibleValueException.java
@@ -0,0 +1,29 @@
+package cz.muni.ics.oidc.models.exceptions;
+
+/**
+ * Exception represents state when value of an attribute cannot be converted into desired type.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class InconvertibleValueException extends RuntimeException {
+
+	public InconvertibleValueException() {
+		super();
+	}
+
+	public InconvertibleValueException(String s) {
+		super(s);
+	}
+
+	public InconvertibleValueException(String s, Throwable throwable) {
+		super(s, throwable);
+	}
+
+	public InconvertibleValueException(Throwable throwable) {
+		super(throwable);
+	}
+
+	protected InconvertibleValueException(String s, Throwable throwable, boolean b, boolean b1) {
+		super(s, throwable, b, b1);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/mappers/RpcMapper.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/mappers/RpcMapper.java
new file mode 100644
index 000000000..9375a2fdf
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/models/mappers/RpcMapper.java
@@ -0,0 +1,541 @@
+package cz.muni.ics.oidc.models.mappers;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import cz.muni.ics.oidc.exceptions.MissingFieldException;
+import cz.muni.ics.oidc.models.AttributeMapping;
+import cz.muni.ics.oidc.models.Aup;
+import cz.muni.ics.oidc.models.ExtSource;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.models.Member;
+import cz.muni.ics.oidc.models.PerunAttribute;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.models.Resource;
+import cz.muni.ics.oidc.models.UserExtSource;
+import cz.muni.ics.oidc.models.Vo;
+import cz.muni.ics.oidc.models.enums.MemberStatus;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.springframework.util.StringUtils;
+
+
+/**
+ * This class is mapping JsonNodes to object models.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class RpcMapper {
+
+	public static final String ID = "id";
+	public static final String UUID = "uuid";
+	public static final String FIRST_NAME = "firstName";
+	public static final String LAST_NAME = "lastName";
+	public static final String PARENT_GROUP_ID = "parentGroupId";
+	public static final String NAME = "name";
+	public static final String DESCRIPTION = "description";
+	public static final String VO_ID = "voId";
+	public static final String USER_ID = "userId";
+	public static final String STATUS = "status";
+	public static final String FACILITY_ID = "facilityId";
+	public static final String TYPE = "type";
+	public static final String SHORT_NAME = "shortName";
+	public static final String LOGIN = "login";
+	public static final String EXT_SOURCE = "extSource";
+	public static final String LOA = "loa";
+	public static final String LAST_ACCESS = "lastAccess";
+	public static final String PERSISTENT = "persistent";
+	public static final String FRIENDLY_NAME = "friendlyName";
+	public static final String NAMESPACE = "namespace";
+	public static final String DISPLAY_NAME = "displayName";
+	public static final String WRITABLE = "writable";
+	public static final String UNIQUE = "unique";
+	public static final String ENTITY = "entity";
+	public static final String BASE_FRIENDLY_NAME = "baseFriendlyName";
+	public static final String FRIENDLY_NAME_PARAMETER = "friendlyNameParameter";
+	public static final String VALUE = "value";
+	public static final String VALUE_CREATED_AT = "valueCreatedAt";
+	public static final String VALUE_MODIFIED_AT = "valueModifiedAt";
+	public static final String VERSION = "version";
+	public static final String DATE = "date";
+	public static final String LINK = "link";
+	public static final String TEXT = "text";
+
+	/**
+	 * Maps JsonNode to User model.
+	 *
+	 * @param json User in JSON format from Perun to be mapped.
+	 * @return Mapped User object.
+	 */
+	public static PerunUser mapPerunUser(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		String firstName = getFieldAsString(json, FIRST_NAME);
+		String lastName = getRequiredFieldAsString(json, LAST_NAME);
+
+		return new PerunUser(id, firstName, lastName);
+	}
+
+	/**
+	 * Maps JsonNode to List of USERS.
+	 *
+	 * @param jsonArray JSON array of users in JSON format from Perun to be mapped.
+	 * @return List of users.
+	 */
+	public static List<PerunUser> mapPerunUsers(JsonNode jsonArray) {
+		if (jsonArray.isNull()) {
+			return new ArrayList<>();
+		}
+
+		return IntStream.range(0, jsonArray.size()).
+				mapToObj(jsonArray::get).
+				map(RpcMapper::mapPerunUser).
+				collect(Collectors.toList());
+	}
+
+	/**
+	 * Maps JsonNode to Group model.
+	 *
+	 * @param json Group in JSON format from Perun to be mapped.
+	 * @return Mapped Group object.
+	 */
+	public static Group mapGroup(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		Long parentGroupId = getFieldAsLong(json, PARENT_GROUP_ID);
+		String name = getRequiredFieldAsString(json, NAME);
+		String description = getFieldAsString(json, DESCRIPTION);
+		Long voId = getRequiredFieldAsLong(json, VO_ID);
+		String uuid = getRequiredFieldAsString(json, UUID);
+
+		return new Group(id, parentGroupId, name, description, null, uuid,  voId);
+	}
+
+	/**
+	 * Maps JsonNode to List of Groups.
+	 *
+	 * @param jsonArray JSON array of groups in JSON format from Perun to be mapped.
+	 * @return List of groups.
+	 */
+	public static List<Group> mapGroups(JsonNode jsonArray) {
+		if (jsonArray.isNull()) {
+			return new ArrayList<>();
+		}
+
+		List<Group> result = new ArrayList<>();
+		for (int i = 0; i < jsonArray.size(); i++) {
+			JsonNode groupNode = jsonArray.get(i);
+			Group mappedGroup = RpcMapper.mapGroup(groupNode);
+			result.add(mappedGroup);
+		}
+
+		return result;
+	}
+
+	/**
+	 * Maps JsonNode to Facility model.
+	 *
+	 * @param json Facility in JSON format from Perun to be mapped.
+	 * @return Mapped Facility object.
+	 */
+	public static Facility mapFacility(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		String name = getRequiredFieldAsString(json, NAME);
+		String description = getFieldAsString(json, DESCRIPTION);
+
+		return new Facility(id, name, description);
+	}
+
+	/**
+	 * Maps JsonNode to List of Facilities.
+	 *
+	 * @param jsonArray JSON array of facilities in JSON format from Perun to be mapped.
+	 * @return List of facilities.
+	 */
+	public static List<Facility> mapFacilities(JsonNode jsonArray) {
+		if (jsonArray.isNull()) {
+			return new ArrayList<>();
+		}
+
+		List<Facility> result = new ArrayList<>();
+		for (int i = 0; i < jsonArray.size(); i++) {
+			JsonNode facilityNode = jsonArray.get(i);
+			Facility mappedFacility = RpcMapper.mapFacility(facilityNode);
+			result.add(mappedFacility);
+		}
+
+		return result;
+	}
+
+	/**
+	 * Maps JsonNode to Member model.
+	 *
+	 * @param json Member in JSON format from Perun to be mapped.
+	 * @return Mapped Member object.
+	 */
+	public static Member mapMember(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		Long userId = getRequiredFieldAsLong(json, USER_ID);
+		Long voId = getRequiredFieldAsLong(json, VO_ID);
+		MemberStatus status = MemberStatus.fromString(getRequiredFieldAsString(json, STATUS));
+
+		return new Member(id, userId, voId, status);
+	}
+
+	/**
+	 * Maps JsonNode to List of Members.
+	 *
+	 * @param jsonArray JSON array of members in JSON format from Perun to be mapped.
+	 * @return List of members.
+	 */
+	public static List<Member> mapMembers(JsonNode jsonArray) {
+		if (jsonArray.isNull()) {
+			return new ArrayList<>();
+		}
+
+		List<Member> members = new ArrayList<>();
+		for (int i = 0; i < jsonArray.size(); i++) {
+			JsonNode memberNode = jsonArray.get(i);
+			Member mappedMember = RpcMapper.mapMember(memberNode);
+			members.add(mappedMember);
+		}
+
+		return members;
+	}
+
+	/**
+	 * Maps JsonNode to Resource model.
+	 *
+	 * @param json Resource in JSON format from Perun to be mapped.
+	 * @return Mapped Resource object.
+	 */
+	public static Resource mapResource(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		Long voId = getRequiredFieldAsLong(json, VO_ID);
+		Long facilityId = getRequiredFieldAsLong(json, FACILITY_ID);
+		String name = getRequiredFieldAsString(json, NAME);
+		String description = getFieldAsString(json, DESCRIPTION);
+
+		Vo vo = null;
+		if (json.hasNonNull("vo")) {
+			JsonNode voJson = json.get("vo");
+			vo = RpcMapper.mapVo(voJson);
+		}
+
+		return new Resource(id, voId, facilityId, name, description, vo);
+	}
+
+	/**
+	 * Maps JsonNode to List of Resources.
+	 *
+	 * @param jsonArray JSON array of resources in JSON format from Perun to be mapped.
+	 * @return List of resources.
+	 */
+	public static List<Resource> mapResources(JsonNode jsonArray) {
+		if (jsonArray.isNull()) {
+			return new ArrayList<>();
+		}
+
+		List<Resource> resources = new ArrayList<>();
+		for (int i = 0; i < jsonArray.size(); i++) {
+			JsonNode resource = jsonArray.get(i);
+			Resource mappedResource = RpcMapper.mapResource(resource);
+			resources.add(mappedResource);
+		}
+
+		return resources;
+	}
+
+	/**
+	 * Maps JsonNode to ExtSource model.
+	 *
+	 * @param json ExtSource in JSON format from Perun to be mapped.
+	 * @return Mapped ExtSource object.
+	 */
+	public static ExtSource mapExtSource(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		String name = getRequiredFieldAsString(json, NAME);
+		String type = getRequiredFieldAsString(json, TYPE);
+
+		return new ExtSource(id, name, type);
+	}
+
+	/**
+	 * Maps JsonNode to List of ExtSources.
+	 *
+	 * @param jsonArray JSON array of extSources in JSON format from Perun to be mapped.
+	 * @return List of extSources.
+	 */
+	public static List<ExtSource> mapExtSources(JsonNode jsonArray) {
+		if (jsonArray.isNull()) {
+			return new ArrayList<>();
+		}
+
+		List<ExtSource> extSources = new ArrayList<>();
+
+		for (int i = 0; i < jsonArray.size(); i++) {
+			JsonNode extSource = jsonArray.get(i);
+			ExtSource mappedExtSource = RpcMapper.mapExtSource(extSource);
+			extSources.add(mappedExtSource);
+		}
+
+		return extSources;
+	}
+
+	/**
+	 * Maps JsonNode to VO model.
+	 *
+	 * @param json VO in JSON format from Perun to be mapped.
+	 * @return Mapped VO object.
+	 */
+	public static Vo mapVo(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		String name = getRequiredFieldAsString(json, NAME);
+		String shortName = getRequiredFieldAsString(json, SHORT_NAME);
+
+		return new Vo(id, name, shortName);
+	}
+
+	/**
+	 * Maps JsonNode to List of VOs.
+	 *
+	 * @param jsonArray JSON array of VOs in JSON format from Perun to be mapped.
+	 * @return List of VOs.
+	 */
+	public static List<Vo> mapVos(JsonNode jsonArray) {
+		if (jsonArray.isNull()) {
+			return new ArrayList<>();
+		}
+
+		List<Vo> vos = new ArrayList<>();
+
+		for (int i = 0; i < jsonArray.size(); i++) {
+			JsonNode voJson = jsonArray.get(i);
+			Vo mappedVo = RpcMapper.mapVo(voJson);
+			vos.add(mappedVo);
+		}
+
+		return vos;
+	}
+
+	/**
+	 * Maps JsonNode to UserExtSource model.
+	 *
+	 * @param json UserExtSource in JSON format from Perun to be mapped.
+	 * @return Mapped UserExtSource object.
+	 */
+	public static UserExtSource mapUserExtSource(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		String login = getRequiredFieldAsString(json, LOGIN);
+		ExtSource extSource = RpcMapper.mapExtSource(getRequiredFieldAsJsonNode(json, EXT_SOURCE));
+		int loa = getRequiredFieldAsInt(json, LOA);
+		boolean persistent = getRequiredFieldAsBoolean(json, PERSISTENT);
+		Timestamp lastAccess = Timestamp.valueOf(getRequiredFieldAsString(json, LAST_ACCESS));
+
+		return new UserExtSource(id, extSource, login, loa, persistent, lastAccess);
+	}
+
+	/**
+	 * Maps JsonNode to List of UserExtSources.
+	 *
+	 * @param jsonArray JSON array of userExtSources in JSON format from Perun to be mapped.
+	 * @return List of userExtSources.
+	 */
+	public static List<UserExtSource> mapUserExtSources(JsonNode jsonArray) {
+		if (jsonArray.isNull()) {
+			return new ArrayList<>();
+		}
+
+		List<UserExtSource> userExtSources = new ArrayList<>();
+
+		for (int i = 0; i < jsonArray.size(); i++) {
+			JsonNode userExtSource = jsonArray.get(i);
+			UserExtSource mappedUes = RpcMapper.mapUserExtSource(userExtSource);
+			userExtSources.add(mappedUes);
+		}
+
+		return userExtSources;
+	}
+
+	/**
+	 * Maps JsonNode to PerunAttribute model.
+	 *
+	 * @param json PerunAttribute in JSON format from Perun to be mapped.
+	 * @return Mapped PerunAttribute object.
+	 */
+	public static PerunAttribute mapAttribute(JsonNode json) {
+		if (json == null || json.isNull()) {
+			return null;
+		}
+
+		Long id = getRequiredFieldAsLong(json, ID);
+		String friendlyName = getRequiredFieldAsString(json, FRIENDLY_NAME);
+		String namespace = getRequiredFieldAsString(json, NAMESPACE);
+		String description = getFieldAsString(json, DESCRIPTION);
+		String type = getRequiredFieldAsString(json, TYPE);
+		String displayName = getRequiredFieldAsString(json, DISPLAY_NAME);
+		boolean writable = getRequiredFieldAsBoolean(json, WRITABLE);
+		boolean unique = getRequiredFieldAsBoolean(json, UNIQUE);
+		String entity = getRequiredFieldAsString(json, ENTITY);
+		String baseFriendlyName = getFieldAsString(json, BASE_FRIENDLY_NAME);
+		String friendlyNameParameter = getFieldAsString(json, FRIENDLY_NAME_PARAMETER);
+		JsonNode value = getFieldAsJsonNode(json, VALUE);
+		String valueCreatedAt = getFieldAsString(json, VALUE_CREATED_AT);
+		if (!StringUtils.hasText(valueCreatedAt)) {
+			valueCreatedAt = null;
+		}
+		String valueModifiedAt = getFieldAsString(json, VALUE_MODIFIED_AT);
+		if (!StringUtils.hasText(valueModifiedAt)) {
+			valueModifiedAt = null;
+		}
+
+		return new PerunAttribute(id, friendlyName, namespace, description, type, displayName,
+				writable, unique, entity, baseFriendlyName, friendlyNameParameter, value, valueCreatedAt, valueModifiedAt);
+	}
+
+	/**
+	 * Map JsonNode to Map of Perun attributes
+	 * @param jsonNode attributes as array in JSON format to be mapped
+	 * @return Map of PerunAttributes mapped from JsonNode, where key = URN, value = Attribute
+	 */
+	public static Map<String, PerunAttribute> mapAttributes(JsonNode jsonNode, Set<AttributeMapping> attrMappings) {
+		Map<String, PerunAttribute> res = new HashMap<>();
+		Map<String, PerunAttribute> attributesAsMap = new HashMap<>();
+
+		for (int i = 0; i < jsonNode.size(); i++) {
+			JsonNode attribute = jsonNode.get(i);
+			PerunAttribute mappedAttribute = mapAttribute(attribute);
+			attributesAsMap.put(mappedAttribute.getUrn(), mappedAttribute);
+		}
+
+		for (AttributeMapping mapping: attrMappings) {
+			String attrKey = mapping.getRpcName();
+			if (attributesAsMap.containsKey(attrKey)) {
+				PerunAttribute attribute = attributesAsMap.get(attrKey);
+				res.put(mapping.getIdentifier(), attribute);
+			} else {
+				res.put(mapping.getIdentifier(), null);
+			}
+		}
+
+		return res;
+	}
+
+	/**
+	 * Map JsonNode to list of Aup
+	 * @param json Aup in JSON format
+	 * @return Mapped Aup
+	 */
+	public static Aup mapAup(JsonNode json) {
+		Aup aup = new Aup();
+		aup.setVersion(getFieldAsString(json, VERSION));
+		aup.setDate(getFieldAsString(json, DATE));
+		aup.setLink(getFieldAsString(json, LINK));
+		aup.setText(getFieldAsString(json, TEXT));
+		aup.setSignedOn(json.hasNonNull(Aup.SIGNED_ON) ? json.get(Aup.SIGNED_ON).asText() : null);
+		return aup;
+	}
+
+	private static Long getRequiredFieldAsLong(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			throw new MissingFieldException();
+		}
+		return json.get(name).asLong();
+	}
+
+	private static Long getFieldAsLong(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			return 0L;
+		}
+		return json.get(name).asLong();
+	}
+
+	private static int getRequiredFieldAsInt(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			throw new MissingFieldException();
+		}
+		return json.get(name).asInt();
+	}
+
+	private static int getFieldAsInt(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			return 0;
+		}
+		return json.get(name).asInt();
+	}
+
+	private static boolean getRequiredFieldAsBoolean(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			throw new MissingFieldException();
+		}
+		return json.get(name).asBoolean();
+	}
+
+	private static boolean getFieldAsBoolean(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			return false;
+		}
+		return json.get(name).asBoolean();
+	}
+
+	private static String getRequiredFieldAsString(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			throw new MissingFieldException();
+		}
+		return json.get(name).asText();
+	}
+
+	private static String getFieldAsString(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			return "";
+		}
+		return json.get(name).asText();
+	}
+
+	private static JsonNode getRequiredFieldAsJsonNode(JsonNode json, String name) {
+		if (!json.hasNonNull(name)) {
+			throw new MissingFieldException();
+		}
+		return json.get(name);
+	}
+
+	private static JsonNode getFieldAsJsonNode(JsonNode json, String name) {
+		return json.get(name);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/IdpMetadataBeans.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/IdpMetadataBeans.java
new file mode 100644
index 000000000..e3f281469
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/IdpMetadataBeans.java
@@ -0,0 +1,44 @@
+package cz.muni.ics.oidc.saml;
+
+import java.io.File;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.params.HttpClientParams;
+import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider;
+import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
+import org.opensaml.saml2.metadata.provider.MetadataProvider;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
+import org.opensaml.xml.parse.ParserPool;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.saml.metadata.ExtendedMetadata;
+import org.springframework.security.saml.metadata.ExtendedMetadataDelegate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+@Component
+public class IdpMetadataBeans {
+
+    @Bean(name = "idpMetadata")
+    public ExtendedMetadataDelegate idpMetadata(SamlProperties samlProperties,
+                                             ExtendedMetadata extendedMetadata,
+                                             ParserPool parserPool) throws MetadataProviderException
+    {
+        MetadataProvider mp;
+        if (StringUtils.hasText(samlProperties.getIdpMetadataUrl())) {
+            HttpClientParams clientParams = new HttpClientParams();
+            clientParams.setSoTimeout(60000);
+            HttpClient httpClient = new HttpClient(clientParams);
+
+            HTTPMetadataProvider httpMp = new HTTPMetadataProvider(null, httpClient,
+                    samlProperties.getIdpMetadataUrl());
+            httpMp.setParserPool(parserPool);
+            mp = httpMp;
+        } else {
+            FilesystemMetadataProvider fsmp = new FilesystemMetadataProvider(
+                    new File(samlProperties.getIdpMetadataFile()));
+            fsmp.setParserPool(parserPool);
+            mp = fsmp;
+        }
+        return new ExtendedMetadataDelegate(mp, extendedMetadata);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunOidcLogoutSuccessHandler.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunOidcLogoutSuccessHandler.java
new file mode 100644
index 000000000..2598aa988
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunOidcLogoutSuccessHandler.java
@@ -0,0 +1,52 @@
+package cz.muni.ics.oidc.saml;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_POST_LOGOUT_REDIRECT_URI;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_STATE;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class PerunOidcLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunOidcLogoutSuccessHandler.class);
+
+    @Override
+    protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
+        String targetUrl = super.determineTargetUrl(request, response);
+        if (getDefaultTargetUrl().equals(targetUrl)) {
+            String referer = request.getHeader("Referer");
+            if (StringUtils.hasText(referer)) {
+                try {
+                    MultiValueMap<String, String> params = UriComponentsBuilder.fromHttpUrl(referer)
+                            .build().getQueryParams();
+                    UriComponentsBuilder builder;
+                    if (params != null && params.containsKey(PARAM_POST_LOGOUT_REDIRECT_URI)) {
+                        String postLogoutRedirectUri = params.getFirst(PARAM_POST_LOGOUT_REDIRECT_URI);
+                        log.trace("Reconstruct from post_logout_redirect_uri: {}", postLogoutRedirectUri);
+                        postLogoutRedirectUri = URLDecoder.decode(postLogoutRedirectUri, StandardCharsets.UTF_8.toString());
+                        builder = UriComponentsBuilder.fromUriString(postLogoutRedirectUri);
+                        if (params.containsKey(PARAM_STATE) && StringUtils.hasText(params.getFirst(PARAM_STATE))) {
+                            String state = params.getFirst(PARAM_STATE);
+                            log.trace("Add state param to target: {}", state);
+                            builder.queryParam(PARAM_STATE, state);
+                        }
+                        targetUrl = builder.build().toString();
+                    }
+                } catch (IllegalArgumentException | UnsupportedEncodingException e) {
+                    log.debug("Invalid URL or error has occurred when parsing it, do not use it and fall back");
+                }
+            }
+        }
+        return targetUrl;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlAuthenticationProvider.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlAuthenticationProvider.java
new file mode 100644
index 000000000..4f043ed0d
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlAuthenticationProvider.java
@@ -0,0 +1,51 @@
+package cz.muni.ics.oidc.saml;
+
+import cz.muni.ics.oidc.models.PerunUser;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.saml.SAMLAuthenticationProvider;
+import org.springframework.security.saml.SAMLCredential;
+
+public class PerunSamlAuthenticationProvider extends SAMLAuthenticationProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunSamlAuthenticationProvider.class);
+
+    private static final GrantedAuthority ROLE_USER = new SimpleGrantedAuthority("ROLE_USER");
+    private static final GrantedAuthority ROLE_ADMIN = new SimpleGrantedAuthority("ROLE_ADMIN");
+
+    private final List<Long> adminIds = new ArrayList<>();
+
+    public PerunSamlAuthenticationProvider(List<String> adminIds) {
+        for (String id : adminIds) {
+            long l = Long.parseLong(id);
+            this.adminIds.add(l);
+            log.debug("added user {} as admin", l);
+        }
+    }
+
+    @Override
+    protected Object getPrincipal(SAMLCredential credential, Object userDetail) {
+        PerunUser user = (PerunUser) userDetail;
+        return new User(String.valueOf(user.getId()), credential.getRemoteEntityID(),
+                getEntitlements(credential, userDetail));
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getEntitlements(SAMLCredential credential, Object userDetail) {
+        PerunUser user = (PerunUser) userDetail;
+        Collection<GrantedAuthority> authorities = new ArrayList<>();
+        authorities.add(ROLE_USER);
+        if (adminIds.contains(user.getId())) {
+            authorities.add(ROLE_ADMIN);
+            log.debug("adding admin role for user with Perun ID: {}", user.getId());
+        }
+        return authorities;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlAuthenticationSuccessHandler.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlAuthenticationSuccessHandler.java
new file mode 100644
index 000000000..47fc6105d
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlAuthenticationSuccessHandler.java
@@ -0,0 +1,57 @@
+package cz.muni.ics.oidc.saml;
+
+import static cz.muni.ics.openid.connect.web.AuthenticationTimeStamper.AUTH_TIMESTAMP;
+
+import cz.muni.ics.oidc.server.PerunOIDCTokenService;
+import java.io.IOException;
+import java.util.Date;
+import java.util.stream.Collectors;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.opensaml.saml2.core.AuthnContext;
+import org.opensaml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml2.core.AuthnStatement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
+import org.springframework.security.saml.SAMLCredential;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.WebAuthenticationDetails;
+
+public class PerunSamlAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
+
+    private final static Logger log = LoggerFactory.getLogger(PerunSamlAuthenticationSuccessHandler.class);
+
+    @Override
+    public void onAuthenticationSuccess(HttpServletRequest request,
+                                        HttpServletResponse response,
+                                        Authentication authentication)
+            throws ServletException, IOException
+    {
+        Date authTimestamp = new Date();
+        request.getSession().setAttribute(AUTH_TIMESTAMP, authTimestamp);
+        if (authentication instanceof ExpiringUsernameAuthenticationToken) {
+            ExpiringUsernameAuthenticationToken token = (ExpiringUsernameAuthenticationToken) authentication;
+            Object details = token.getDetails();
+            if (details instanceof WebAuthenticationDetails) {
+                WebAuthenticationDetails webDetails = (WebAuthenticationDetails) details;
+                log.info("successful authentication, remote IP address {}", webDetails.getRemoteAddress());
+            }
+            setAcrToSession(request, token);
+        }
+        super.onAuthenticationSuccess(request, response, authentication);
+    }
+
+    private void setAcrToSession(HttpServletRequest request, Authentication token) {
+        String acrs = ((SAMLCredential) token.getCredentials()).getAuthenticationAssertion()
+                .getAuthnStatements().stream()
+                .map(AuthnStatement::getAuthnContext)
+                .map(AuthnContext::getAuthnContextClassRef)
+                .map(AuthnContextClassRef::getAuthnContextClassRef)
+                .collect(Collectors.joining());
+        request.getSession(true).setAttribute(PerunOIDCTokenService.SESSION_PARAM_ACR, acrs);;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlEntryPoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlEntryPoint.java
new file mode 100644
index 000000000..4f3b301a6
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlEntryPoint.java
@@ -0,0 +1,248 @@
+package cz.muni.ics.oidc.saml;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.EFILTER_PREFIX;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.FILTER_PREFIX;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.IDP_ENTITY_ID_PREFIX;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_PROMPT;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.REFEDS_MFA;
+
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.server.filters.PerunFilterConstants;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.opensaml.common.SAMLException;
+import org.opensaml.saml2.metadata.AssertionConsumerService;
+import org.opensaml.saml2.metadata.SPSSODescriptor;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
+import org.opensaml.ws.message.encoder.MessageEncodingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.saml.SAMLConstants;
+import org.springframework.security.saml.SAMLEntryPoint;
+import org.springframework.security.saml.context.SAMLMessageContext;
+import org.springframework.security.saml.util.SAMLUtil;
+import org.springframework.security.saml.websso.WebSSOProfileOptions;
+import org.springframework.util.StringUtils;
+
+public class PerunSamlEntryPoint extends SAMLEntryPoint {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunSamlEntryPoint.class);
+
+    private final PerunAdapter perunAdapter;
+    private final PerunOidcConfig config;
+    private final FacilityAttrsConfig facilityAttrsConfig;
+
+    @Autowired
+    public PerunSamlEntryPoint(PerunAdapter perunAdapter, PerunOidcConfig config,
+                               FacilityAttrsConfig facilityAttrsConfig)
+    {
+        this.perunAdapter = perunAdapter;
+        this.config = config;
+        this.facilityAttrsConfig = facilityAttrsConfig;
+    }
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
+            throws IOException, ServletException {
+        try {
+            SAMLMessageContext context = contextProvider.getLocalAndPeerEntity(request, response);
+
+            if (isECP(context)) {
+                initializeECP(request, context, e);
+            } else if (isDiscovery(context)) {
+                initializeDiscovery(context);
+            } else {
+                initializeSSO(request, context, e);
+            }
+        } catch (SAMLException | MetadataProviderException | MessageEncodingException e1) {
+            log.debug("Error initializing entry point", e1);
+            throw new ServletException(e1);
+        }
+    }
+
+    protected WebSSOProfileOptions getExtendedOptions(HttpServletRequest request,
+                                                      SAMLMessageContext context,
+                                                      AuthenticationException exception)
+            throws MetadataProviderException
+    {
+        WebSSOProfileOptions options = super.getProfileOptions(context, exception);
+        addExtraParams(request, options);
+        return options;
+    }
+
+
+    // copy from super class to call our getProfileOptions
+    protected void initializeECP(HttpServletRequest request, SAMLMessageContext context,
+                                 AuthenticationException e)
+            throws MetadataProviderException, SAMLException, MessageEncodingException
+    {
+        WebSSOProfileOptions options = getExtendedOptions(request, context, e);
+        log.debug("Processing SSO using ECP profile");
+        webSSOprofileECP.sendAuthenticationRequest(context, options);
+        samlLogger.log(SAMLConstants.AUTH_N_REQUEST, SAMLConstants.SUCCESS, context);
+    }
+
+    // copy from super class to call our getProfileOptions
+    protected void initializeSSO(HttpServletRequest request, SAMLMessageContext context,
+                                 AuthenticationException e)
+            throws MetadataProviderException, SAMLException, MessageEncodingException
+    {
+        WebSSOProfileOptions options = getExtendedOptions(request, context, e);
+        AssertionConsumerService consumerService = SAMLUtil.getConsumerService(
+                (SPSSODescriptor) context.getLocalEntityRoleMetadata(), options.getAssertionConsumerIndex());
+
+        // HoK WebSSO
+        if (SAMLConstants.SAML2_HOK_WEBSSO_PROFILE_URI.equals(consumerService.getBinding())) {
+            if (webSSOprofileHoK == null) {
+                log.warn("WebSSO HoK profile was specified to be used, but profile is not " +
+                    "configured in the EntryPoint, HoK will be skipped");
+            } else {
+                log.debug("Processing SSO using WebSSO HolderOfKey profile");
+                webSSOprofileHoK.sendAuthenticationRequest(context, options);
+                samlLogger.log(SAMLConstants.AUTH_N_REQUEST, SAMLConstants.SUCCESS, context);
+                return;
+            }
+        }
+
+        // Ordinary WebSSO
+        log.debug("Processing SSO using WebSSO profile");
+        webSSOprofile.sendAuthenticationRequest(context, options);
+        samlLogger.log(SAMLConstants.AUTH_N_REQUEST, SAMLConstants.SUCCESS, context);
+    }
+
+    private void addExtraParams(HttpServletRequest request, WebSSOProfileOptions options) {
+        log.debug("Transforming OIDC params to SAML");
+        processAcrValues(request, options);
+        processForceAuthn(request, options);
+        processPrompt(request, options);
+    }
+
+    private void processForceAuthn(HttpServletRequest request, WebSSOProfileOptions options) {
+        if (PerunSamlUtils.needsReAuthByForceAuthn(request)) {
+            log.debug("Transformed forceAuthn parameter to SAML forceAuthn=true");
+            options.setForceAuthN(true);
+        }
+    }
+
+    private void processPrompt(HttpServletRequest request, WebSSOProfileOptions options) {
+        if (PerunSamlUtils.needsReAuthByPrompt(request)) {
+            log.debug("Transformed prompt parameter ({}) to SAML forceAuthn=true",
+                request.getParameter(PARAM_PROMPT));
+            options.setForceAuthN(true);
+        }
+    }
+
+    private void processAcrValues(HttpServletRequest request, WebSSOProfileOptions options) {
+        String acrValues = request.getParameter(PerunFilterConstants.PARAM_ACR_VALUES);
+        log.debug("Processing acr_values parameter: {}", acrValues);
+        List<String> acrs = convertAcrValuesToList(acrValues);
+
+        if (!hasAcrForcingIdp(acrs)) {
+            String clientId = request.getParameter(PerunFilterConstants.PARAM_CLIENT_ID);
+            String idpFilter = extractIdpFilterForRp(clientId);
+            if (idpFilter != null) {
+                log.debug("Added IdP filter as SAML AuthnContextClassRef ({})", idpFilter);
+                acrs.add(idpFilter);
+            }
+        }
+
+        if (PerunSamlUtils.needsReAuthByMfa(request)) {
+            log.debug("ACRs include {}, added forceAuthn to proxy request", REFEDS_MFA);
+            options.setForceAuthN(true);
+        }
+
+        if (acrs.size() > 0) {
+            options.setAuthnContexts(acrs);
+            log.debug("Transformed acr_values ({}) to SAML AuthnContextClassRef ({})",
+                acrValues, options.getAuthnContexts());
+        }
+    }
+
+    private List<String> convertAcrValuesToList(String acrValues) {
+        List<String> acrs = new ArrayList<>();
+        if (StringUtils.hasText(acrValues)) {
+            String[] parts = acrValues.split(" ");
+            if (parts.length > 0) {
+                for (String acr: parts) {
+                    if (StringUtils.hasText(acr)) {
+                        acrs.add(acr);
+                    }
+                }
+            }
+        }
+        return acrs;
+    }
+
+    private boolean hasAcrForcingIdp(List<String> acrs) {
+        boolean hasIdpEntityId = acrs != null
+            && !acrs.isEmpty()
+            && acrs.stream().anyMatch(
+            acr -> StringUtils.hasText(acr) && acr.startsWith(IDP_ENTITY_ID_PREFIX)
+        );
+
+        if (hasIdpEntityId) {
+            log.debug("Request contains ACR to force specific IdP, no configured IdP filter will be used");
+        }
+        return hasIdpEntityId;
+    }
+
+    private String extractIdpFilterForRp(String clientId) {
+        if (!config.isAskPerunForIdpFiltersEnabled()) {
+            return null;
+        }
+        Facility facility = null;
+        if (clientId != null) {
+            facility = perunAdapter.getFacilityByClientId(clientId);
+        }
+        Map<String, PerunAttributeValue> filterAttributes = new HashMap<>();
+        if (facility != null) {
+            filterAttributes = this.getFacilityFilterAttributes(facility);
+        }
+
+        String idpFilterAcr = null;
+        String idpFilter = fetchRpIdpFilter(filterAttributes, facilityAttrsConfig.getWayfFilterAttr());
+        if (StringUtils.hasText(idpFilter)) {
+            idpFilterAcr = FILTER_PREFIX + idpFilter;
+        } else {
+            String idpEFilter = fetchRpIdpFilter(filterAttributes, facilityAttrsConfig.getWayfEFilterAttr());
+            if (StringUtils.hasText(idpEFilter)) {
+                idpFilterAcr = EFILTER_PREFIX + idpEFilter;
+            }
+        }
+        return idpFilterAcr;
+    }
+
+    private Map<String, PerunAttributeValue> getFacilityFilterAttributes(Facility facility) {
+        if (facility != null && facility.getId() != null) {
+            List<String> attrsToFetch = new ArrayList<>();
+            attrsToFetch.add(facilityAttrsConfig.getWayfEFilterAttr());
+            attrsToFetch.add(facilityAttrsConfig.getWayfFilterAttr());
+            return perunAdapter.getFacilityAttributeValues(facility, attrsToFetch);
+        }
+        return new HashMap<>();
+    }
+
+    private String fetchRpIdpFilter(Map<String, PerunAttributeValue> filterAttributes, String attrName) {
+        String result = null;
+        if (filterAttributes.get(attrName) != null) {
+            PerunAttributeValue filterAttribute = filterAttributes.get(attrName);
+            if (filterAttribute != null && filterAttribute.valueAsString() != null) {
+                result = filterAttribute.valueAsString();
+            }
+        }
+        return result;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlProcessingFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlProcessingFilter.java
new file mode 100644
index 000000000..17b68dd52
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlProcessingFilter.java
@@ -0,0 +1,50 @@
+package cz.muni.ics.oidc.saml;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.saml.SAMLProcessingFilter;
+
+public class PerunSamlProcessingFilter extends SAMLProcessingFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunSamlProcessingFilter.class);
+
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+        throws IOException, ServletException {
+        log.debug("doFilter");
+        super.doFilter(req, res, chain);
+    }
+
+    @Override
+    protected void successfulAuthentication(HttpServletRequest request,
+                                            HttpServletResponse response, FilterChain chain,
+                                            Authentication authResult)
+        throws IOException, ServletException {
+        log.debug("successful authentication");
+        super.successfulAuthentication(request, response, chain, authResult);
+    }
+
+    @Override
+    protected void unsuccessfulAuthentication(HttpServletRequest request,
+                                              HttpServletResponse response,
+                                              AuthenticationException failed)
+        throws IOException, ServletException {
+        log.debug("unsuccessfull authentication");
+        super.unsuccessfulAuthentication(request, response, failed);
+    }
+
+    @Override
+    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+        log.debug("Attempting authentication");
+        return super.attemptAuthentication(request, response);
+    }
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlUserDetailsService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlUserDetailsService.java
new file mode 100644
index 000000000..612b23f2c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlUserDetailsService.java
@@ -0,0 +1,32 @@
+package cz.muni.ics.oidc.saml;
+
+import cz.muni.ics.oidc.server.PerunPrincipal;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.filters.FiltersUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.saml.SAMLCredential;
+import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
+
+public class PerunSamlUserDetailsService implements SAMLUserDetailsService {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunSamlUserDetailsService.class);
+
+    private final PerunAdapter perunAdapter;
+
+    @Autowired
+    public PerunSamlUserDetailsService(PerunAdapter perunAdapter) {
+        this.perunAdapter = perunAdapter;
+    }
+
+    @Override
+    public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
+        log.debug("Loading user for SAML credential");
+        PerunPrincipal p = FiltersUtils.getPerunPrincipal(credential);
+        log.debug("Fetching user from perun ({})", p);
+        return perunAdapter.getPreauthenticatedUserId(p);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlUtils.java
new file mode 100644
index 000000000..a9bde3438
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunSamlUtils.java
@@ -0,0 +1,42 @@
+package cz.muni.ics.oidc.saml;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_ACR_VALUES;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_FORCE_AUTHN;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_PROMPT;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PROMPT_LOGIN;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PROMPT_SELECT_ACCOUNT;
+
+import cz.muni.ics.oidc.server.filters.PerunFilterConstants;
+import javax.servlet.ServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+public class PerunSamlUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunSamlUtils.class);
+
+    public static boolean needsReAuthByPrompt(ServletRequest request) {
+        String prompt = request.getParameter(PARAM_PROMPT);
+        boolean res = (StringUtils.hasText(prompt) && (PROMPT_LOGIN.equalsIgnoreCase(prompt)
+            || PROMPT_SELECT_ACCOUNT.equalsIgnoreCase(prompt)));
+        log.debug("requires reAuth by prompt - {}", res);
+        return res;
+    }
+
+    public static boolean needsReAuthByForceAuthn(ServletRequest request) {
+        String forceAuthn = request.getParameter(PARAM_FORCE_AUTHN);
+        boolean res = (StringUtils.hasText(forceAuthn) && Boolean.parseBoolean(forceAuthn));
+        log.debug("requires reAuth by forceAuthn - {}", res);
+        return res;
+    }
+
+    public static boolean needsReAuthByMfa(ServletRequest request) {
+        String acrValues = request.getParameter(PARAM_ACR_VALUES);
+        boolean res = StringUtils.hasText(acrValues)
+            && acrValues.contains(PerunFilterConstants.REFEDS_MFA);
+        log.debug("requires reAuth by MFA acr - {}", res);
+        return res;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunWebSSOProfileConsumerImpl.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunWebSSOProfileConsumerImpl.java
new file mode 100644
index 000000000..1f7f15424
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/PerunWebSSOProfileConsumerImpl.java
@@ -0,0 +1,86 @@
+package cz.muni.ics.oidc.saml;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.opensaml.saml2.core.AuthnContext;
+import org.opensaml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml2.core.AuthnStatement;
+import org.opensaml.saml2.core.RequestedAuthnContext;
+import org.springframework.security.authentication.CredentialsExpiredException;
+import org.springframework.security.authentication.InsufficientAuthenticationException;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.saml.context.SAMLMessageContext;
+import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl;
+
+public class PerunWebSSOProfileConsumerImpl extends WebSSOProfileConsumerImpl {
+
+    private boolean enableComparison = false;
+    private Set<String> reservedPrefixes;
+
+    public void setEnableComparison(boolean enableComparison) {
+        this.enableComparison = enableComparison;
+    }
+
+    public void setReservedPrefixes(Set<String> reservedPrefixes) {
+        this.reservedPrefixes = reservedPrefixes;
+    }
+
+    @Override
+    protected void verifyAuthenticationStatement(AuthnStatement auth,
+                                                 RequestedAuthnContext requestedAuthnContext,
+                                                 SAMLMessageContext context)
+        throws AuthenticationException
+    {
+        // Validate users session is still valid
+        if (auth.getSessionNotOnOrAfter() != null && auth.getSessionNotOnOrAfter().isBeforeNow()) {
+            throw new CredentialsExpiredException("Authentication session is not valid on or after "
+                + auth.getSessionNotOnOrAfter());
+        }
+
+        // Verify context
+        verifyAuthnContext(requestedAuthnContext, auth.getAuthnContext(), context);
+    }
+
+    @Override
+    protected void verifyAuthnContext(RequestedAuthnContext requestedAuthnContext,
+                                      AuthnContext receivedContext, SAMLMessageContext context)
+        throws InsufficientAuthenticationException
+    {
+        if (!enableComparison) {
+            return;
+        }
+        if (filterOutConditionsMet(requestedAuthnContext)) {
+            filterOutPrefixedAcrs(requestedAuthnContext);
+        }
+        super.verifyAuthnContext(requestedAuthnContext, receivedContext, context);
+    }
+
+    private boolean filterOutConditionsMet(RequestedAuthnContext requestedAuthnContext) {
+        if (requestedAuthnContext == null) {
+            return false;
+        } else {
+            List<AuthnContextClassRef> requested = requestedAuthnContext.getAuthnContextClassRefs();
+            return requested != null
+                && !requested.isEmpty()
+                && reservedPrefixes != null
+                && !reservedPrefixes.isEmpty();
+        }
+    }
+
+    private void filterOutPrefixedAcrs(RequestedAuthnContext requestedAuthnContext) {
+        List<AuthnContextClassRef> requested = requestedAuthnContext.getAuthnContextClassRefs();
+        Iterator<AuthnContextClassRef> it = requested.iterator();
+        while (it.hasNext()) {
+            AuthnContextClassRef accr = it.next();
+            if (reservedPrefixes == null || reservedPrefixes.isEmpty()) {
+                continue;
+            }
+            for (String prefix : reservedPrefixes) {
+                if (accr.getAuthnContextClassRef().startsWith(prefix)) {
+                    it.remove();
+                }
+            }
+        }
+    }
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlInvalidateSessionFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlInvalidateSessionFilter.java
new file mode 100644
index 000000000..bee72436d
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlInvalidateSessionFilter.java
@@ -0,0 +1,81 @@
+package cz.muni.ics.oidc.saml;
+
+import static org.springframework.http.HttpHeaders.REFERER;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.GenericFilterBean;
+
+public class SamlInvalidateSessionFilter extends GenericFilterBean {
+
+    private static final Logger log = LoggerFactory.getLogger(SamlInvalidateSessionFilter.class);
+    private final AntPathRequestMatcher matcher;
+
+    private final String idpEntityId;
+    private final String proxySpEntityId;
+    private final boolean proxyEnabled;
+    private final String oidcIssuer;
+    private final SecurityContextLogoutHandler contextLogoutHandler;
+
+    public SamlInvalidateSessionFilter(String pattern,
+                                       String idpEntityId,
+                                       String oidcIssuer,
+                                       boolean proxyEnabled,
+                                       String proxySpEntityId,
+                                       SecurityContextLogoutHandler contextLogoutHandler)
+    {
+        this.matcher = new AntPathRequestMatcher(pattern);
+        this.idpEntityId = idpEntityId;
+        this.oidcIssuer = oidcIssuer;
+        this.proxyEnabled = proxyEnabled;
+        this.proxySpEntityId = proxySpEntityId;
+        this.contextLogoutHandler = contextLogoutHandler;
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+        throws IOException, ServletException
+    {
+        HttpServletRequest req = (HttpServletRequest) request;
+        HttpServletResponse res = (HttpServletResponse) response;
+        if (matcher.matches(req)) {
+            String referer = req.getHeader(REFERER);
+            if (!isInternalReferer(referer)) {
+                log.debug("Got external referer, clear session to reauthenticate");
+                contextLogoutHandler.logout(req, res, null);
+            }
+        }
+        chain.doFilter(req, res);
+    }
+
+    private boolean isInternalReferer(String referer) {
+        if (!StringUtils.hasText(referer)) {
+            // no referer, consider as internal
+            return true;
+        }
+
+        boolean isInternal = referer.startsWith(oidcIssuer);
+        if (!isInternal) {
+            if (proxyEnabled) {
+                // check if referer is PROXY (SP part)
+                isInternal = referer.startsWith(proxySpEntityId);
+            } else {
+                // check if referer is IDP
+                isInternal = referer.startsWith(idpEntityId);
+            }
+        }
+
+        log.debug("Referer {} is internal: {}", referer, isInternal);
+        return isInternal;
+    }
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlProperties.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlProperties.java
new file mode 100644
index 000000000..6ccbcfed4
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/saml/SamlProperties.java
@@ -0,0 +1,128 @@
+package cz.muni.ics.oidc.saml;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.StringUtils;
+
+public class SamlProperties implements InitializingBean {
+
+    private static final Logger log = LoggerFactory.getLogger(SamlProperties.class);
+
+    private String entityID;
+    private String keystoreLocation;
+    private String keystorePassword;
+    private String keystoreDefaultKey;
+    private String keystoreDefaultKeyPassword;
+    private String defaultIdpEntityId;
+    private String idpMetadataFile;
+    private String idpMetadataUrl;
+    private String[] acrReservedPrefixes;
+
+    public String getEntityID() {
+        return entityID;
+    }
+
+    public void setEntityID(String entityID) {
+        this.entityID = entityID;
+    }
+
+    public String getKeystoreLocation() {
+        return keystoreLocation;
+    }
+
+    public void setKeystoreLocation(String keystoreLocation) {
+        this.keystoreLocation = keystoreLocation;
+    }
+
+    public String getKeystorePassword() {
+        return keystorePassword;
+    }
+
+    public void setKeystorePassword(String keystorePassword) {
+        this.keystorePassword = keystorePassword;
+    }
+
+    public String getKeystoreDefaultKey() {
+        return keystoreDefaultKey;
+    }
+
+    public void setKeystoreDefaultKey(String keystoreDefaultKey) {
+        this.keystoreDefaultKey = keystoreDefaultKey;
+    }
+
+    public String getKeystoreDefaultKeyPassword() {
+        return keystoreDefaultKeyPassword;
+    }
+
+    public void setKeystoreDefaultKeyPassword(String keystoreDefaultKeyPassword) {
+        this.keystoreDefaultKeyPassword = keystoreDefaultKeyPassword;
+    }
+
+    public String getDefaultIdpEntityId() {
+        return defaultIdpEntityId;
+    }
+
+    public void setDefaultIdpEntityId(String defaultIdpEntityId) {
+        this.defaultIdpEntityId = defaultIdpEntityId;
+    }
+
+    public String getIdpMetadataFile() {
+        return idpMetadataFile;
+    }
+
+    public void setIdpMetadataFile(String idpMetadataFile) {
+        this.idpMetadataFile = idpMetadataFile;
+    }
+
+    public String getIdpMetadataUrl() {
+        return idpMetadataUrl;
+    }
+
+    public void setIdpMetadataUrl(String idpMetadataUrl) {
+        this.idpMetadataUrl = idpMetadataUrl;
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        if (!StringUtils.hasText(idpMetadataUrl) && !StringUtils.hasText(idpMetadataFile)) {
+            throw new IllegalStateException("No URL nor file provided for metadata");
+        }
+        if (StringUtils.hasText(idpMetadataUrl)) {
+            try {
+                new URL(idpMetadataUrl);
+                return;
+            } catch (MalformedURLException e) {
+                log.warn("'{}' is not a valid URL", idpMetadataUrl);
+            }
+        }
+        File f = new File(idpMetadataFile);
+        if (!f.exists()) {
+            throw new IllegalStateException("File '" + idpMetadataFile + "' does not exist");
+        }
+    }
+
+    public String[] getAcrReservedPrefixes() {
+        return acrReservedPrefixes;
+    }
+
+    public void setAcrReservedPrefixes(String[] acrReservedPrefixes) {
+        if (acrReservedPrefixes == null) {
+            this.acrReservedPrefixes = new String[] {};
+        } else {
+            List<String> nonNull = new ArrayList<>();
+            for (String prefix: acrReservedPrefixes) {
+                if (StringUtils.hasText(prefix)) {
+                    nonNull.add(prefix);
+                }
+            }
+
+            this.acrReservedPrefixes = nonNull.toArray(new String[0]);
+        }
+    }
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/AttributeMappingsService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/AttributeMappingsService.java
new file mode 100644
index 000000000..e9f3de3b1
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/AttributeMappingsService.java
@@ -0,0 +1,120 @@
+package cz.muni.ics.oidc.server;
+
+
+import cz.muni.ics.oidc.models.AttributeMapping;
+import cz.muni.ics.oidc.models.enums.PerunAttrValueType;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Service providing methods to use AttributeMapping objects when fetching attributes.
+ *
+ * Names for the attribute are configured in configuration file in the following way:
+ * (replace [entity] with one of user|vo|facility|resource|group)
+ * <ul>
+ *     <li><b>[entity].attribute_names.customList</b> - comma separated list of names for attributes</li>
+ * </ul>
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class AttributeMappingsService {
+
+	private static final Logger log = LoggerFactory.getLogger(AttributeMappingsService.class);
+
+	private static final String LDAP_NAME = ".mapping.ldap";
+	private static final String RPC_NAME = ".mapping.rpc";
+	private static final String TYPE = ".type";
+	private static final String SEPARATOR = ".separator";
+
+	private final Map<String, AttributeMapping> attributeMap;
+
+	public AttributeMappingsService(String[] attrIdentifiersFixed, String[] attrIdentifiersCustom,
+									Properties attrMappingsProperties) {
+		attributeMap = new HashMap<>();
+
+		if (attrIdentifiersFixed != null) {
+			this.initAttrMappings(attrIdentifiersFixed, attrMappingsProperties);
+		}
+
+		if (attrIdentifiersCustom != null) {
+			this.initAttrMappings(attrIdentifiersCustom, attrMappingsProperties);
+		}
+	}
+
+	/**
+	 * Get AttributeMapping based on the given internal identifier of attribute.
+	 * @param identifier String identifier of the attribute.
+	 * @return AttributeMapping. If invalid identifier is passed (null or unknown) an exception is thrown.
+	 */
+	public AttributeMapping getMappingByIdentifier(String identifier) {
+		if (identifier == null) {
+			throw new IllegalArgumentException("Identifier cannot be null");
+		} else if (!attributeMap.containsKey(identifier)) {
+			return null;
+		}
+
+		return attributeMap.get(identifier);
+	}
+
+	/**
+	 * Get Set of AttributeMapping based on the given internal identifiers of attributes.
+	 * @param identifiers Collection of Strings
+	 * @return Set of AttributeMapping. If invalid identifier is passed inside the collection, this identifier is ignored.
+	 */
+	public Set<AttributeMapping> getMappingsByIdentifiers(Collection<String> identifiers) {
+		Set<AttributeMapping> mappings = new HashSet<>();
+		if (identifiers != null) {
+			for (String identifier : identifiers) {
+				try {
+					mappings.add(getMappingByIdentifier(identifier));
+				} catch (IllegalArgumentException e) {
+					log.warn("Caught {} when getting mappings, check your configuration for identifier {}",
+							e.getClass(), identifier, e);
+				}
+			}
+		}
+
+		return mappings.stream().filter(Objects::nonNull).collect(Collectors.toSet());
+	}
+
+	private void initAttrMappings(String[] attributeIdentifiers, Properties attrProperties) {
+		if (attributeIdentifiers == null || attributeIdentifiers.length <= 0) {
+			return;
+		}
+
+		for (String identifier : attributeIdentifiers) {
+			if (identifier == null || identifier.isEmpty()) {
+				continue;
+			}
+			AttributeMapping am = initAttrMapping(identifier, attrProperties);
+			log.debug("Initialized attributeMapping: {}", am);
+			attributeMap.put(am.getIdentifier(), am);
+		}
+	}
+
+	private AttributeMapping initAttrMapping(String attrIdentifier, Properties attrProperties) {
+		String rpcIdentifier = attrProperties.getProperty(attrIdentifier + RPC_NAME);
+		String ldapIdentifier = attrProperties.getProperty(attrIdentifier + LDAP_NAME);
+		if (ldapIdentifier != null && ldapIdentifier.trim().isEmpty()) {
+			ldapIdentifier = null;
+		}
+
+		String type = attrProperties.getProperty(attrIdentifier + TYPE);
+		String separator = "";
+		if (PerunAttrValueType.MAP_KEY_VALUE.equals(PerunAttrValueType.parse(type))) {
+			separator = attrProperties.getProperty(attrIdentifier + SEPARATOR);
+		}
+
+		return new AttributeMapping(attrIdentifier, rpcIdentifier, ldapIdentifier, type, separator);
+	}
+
+}
+
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/CustomClearTasks.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/CustomClearTasks.java
new file mode 100644
index 000000000..82efdf218
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/CustomClearTasks.java
@@ -0,0 +1,172 @@
+package cz.muni.ics.oidc.server;
+
+import java.time.Instant;
+import java.util.Date;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.QueryTimeoutException;
+import cz.muni.ics.oauth2.model.AuthorizationCodeEntity;
+import cz.muni.ics.oauth2.model.DeviceCode;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.openid.connect.models.Acr;
+import cz.muni.ics.openid.connect.models.DeviceCodeAcr;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public class CustomClearTasks {
+
+    @PersistenceContext(unitName = "defaultPersistenceUnit")
+    private EntityManager manager;
+
+    public int clearExpiredTokens(long timeout) {
+        int count = 0;
+        count += this.clearExpiredAccessTokens(timeout);
+        count += this.clearExpiredRefreshTokens(timeout);
+        count += this.clearOrphanedAuthHolders(timeout);
+        return count;
+    }
+
+    int clearExpiredAccessTokens(long timeout) {
+        manager.flush();
+        manager.clear();
+        int count = 0;
+        Query query1 = manager.createQuery("DELETE FROM OAuth2AccessTokenEntity a " +
+                "WHERE a.expiration <= :" + OAuth2AccessTokenEntity.PARAM_DATE);
+        query1.setParameter(OAuth2AccessTokenEntity.PARAM_DATE, new Date());
+        if (timeout > 0) {
+            query1.setHint("javax.persistence.query.timeout", timeout);
+        }
+        try {
+            count += query1.executeUpdate();
+        } catch (QueryTimeoutException e) {
+            // this is OK
+        }
+        return count;
+    }
+
+    int clearExpiredRefreshTokens(long timeout) {
+        manager.flush();
+        manager.clear();
+        int count = 0;
+        Query query2 = manager.createQuery("DELETE FROM OAuth2RefreshTokenEntity r " +
+                "WHERE r.expiration <= :" + OAuth2RefreshTokenEntity.PARAM_DATE);
+        query2.setParameter(OAuth2RefreshTokenEntity.PARAM_DATE, new Date());
+        if (timeout > 0) {
+            query2.setHint("javax.persistence.query.timeout", timeout);
+        }
+        try {
+            count += query2.executeUpdate();
+        } catch (QueryTimeoutException e) {
+            // this is OK
+        }
+        return count;
+    }
+
+    int clearOrphanedAuthHolders(long timeout) {
+        manager.flush();
+        manager.clear();
+        int count = 0;
+        Query query3 = manager.createQuery("DELETE FROM AuthenticationHolderEntity a " +
+                "WHERE a.id NOT IN (SELECT t.authenticationHolder.id FROM OAuth2AccessTokenEntity t) AND " +
+                "a.id NOT IN (SELECT r.authenticationHolder.id FROM OAuth2RefreshTokenEntity r) AND " +
+                "a.id NOT IN (SELECT c.authenticationHolder.id FROM AuthorizationCodeEntity c)");
+        if (timeout > 0) {
+            query3.setHint("javax.persistence.query.timeout", timeout);
+        }
+        try {
+            count += query3.executeUpdate();
+        } catch (QueryTimeoutException e) {
+            // this is OK
+        }
+        return count;
+    }
+
+    public int clearExpiredSites(long timeout) {
+        manager.flush();
+        manager.clear();
+        int count = 0;
+        Query query = manager.createQuery("DELETE FROM ApprovedSite a WHERE a.timeoutDate <= :date");
+        query.setParameter("date", new Date());
+        if (timeout > 0) {
+            query.setHint("javax.persistence.query.timeout", timeout);
+        }
+        try {
+            count += query.executeUpdate();
+        } catch (QueryTimeoutException e) {
+            // this is OK
+        }
+        return count;
+    }
+
+    public int clearExpiredAuthorizationCodes(long timeout) {
+        manager.flush();
+        manager.clear();
+        int count = 0;
+        Query query = manager.createQuery("DELETE FROM AuthorizationCodeEntity a " +
+                "WHERE a.expiration <= :" + AuthorizationCodeEntity.PARAM_DATE);
+        query.setParameter(AuthorizationCodeEntity.PARAM_DATE, new Date());
+        if (timeout > 0) {
+            query.setHint("javax.persistence.query.timeout", timeout);
+        }
+        try {
+            count += query.executeUpdate();
+        } catch (QueryTimeoutException e) {
+            // this is OK
+        }
+        return count;
+    }
+
+    public int clearExpiredDeviceCodes(long timeout) {
+        manager.flush();
+        manager.clear();
+        int count = 0;
+        Query query = manager.createQuery("DELETE FROM DeviceCode d WHERE d.expiration <= :" + DeviceCode.PARAM_DATE);
+        query.setParameter(DeviceCode.PARAM_DATE, new Date());
+        if (timeout > 0) {
+            query.setHint("javax.persistence.query.timeout", timeout);
+        }
+        try {
+            count += query.executeUpdate();
+        } catch (QueryTimeoutException e) {
+            // this is OK
+        }
+        return count;
+    }
+
+    public int clearExpiredAcrs(long timeout) {
+        manager.flush();
+        manager.clear();
+        int count = 0;
+        Query query = manager.createNamedQuery(Acr.DELETE_EXPIRED);
+        query.setParameter(Acr.PARAM_EXPIRES_AT, Instant.now().toEpochMilli());
+        if (timeout > 0) {
+            query.setHint("javax.persistence.query.timeout", timeout);
+        }
+        try {
+            count += query.executeUpdate();
+        } catch (QueryTimeoutException e) {
+            // this is OK
+        }
+        return count;
+    }
+
+    public int clearExpiredDeviceCodeAcrs(long timeout) {
+        manager.flush();
+        manager.clear();
+        int count = 0;
+        Query query = manager.createNamedQuery(DeviceCodeAcr.DELETE_EXPIRED);
+        query.setParameter(DeviceCodeAcr.PARAM_EXPIRES_AT, Instant.now().toEpochMilli());
+        if (timeout > 0) {
+            query.setHint("javax.persistence.query.timeout", timeout);
+        }
+        try {
+            count += query.executeUpdate();
+        } catch (QueryTimeoutException e) {
+            // this is OK
+        }
+        return count;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/CustomTaskScheduler.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/CustomTaskScheduler.java
new file mode 100644
index 000000000..3d29523ca
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/CustomTaskScheduler.java
@@ -0,0 +1,140 @@
+package cz.muni.ics.oidc.server;
+
+import java.util.concurrent.TimeUnit;
+import javax.sql.DataSource;
+import net.javacrumbs.shedlock.core.LockAssert;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
+import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
+import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * A custom scheduler for tasks with usage of ShedLock.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Configuration
+@EnableScheduling
+@EnableSchedulerLock(defaultLockAtMostFor = "30s")
+public class CustomTaskScheduler {
+
+	private static final long ONE_MINUTE = 60000L;
+
+	private static final Logger log = LoggerFactory.getLogger(CustomTaskScheduler.class);
+
+	private final CustomClearTasks customClearTasks;
+	private final DataSource dataSource;
+
+	@Autowired
+	public CustomTaskScheduler(CustomClearTasks customClearTasks,
+							   @Qualifier("dataSource") DataSource dataSource)
+	{
+		this.customClearTasks = customClearTasks;
+		this.dataSource = dataSource;
+	}
+
+	@Bean
+	public LockProvider lockProvider() {
+		return new JdbcTemplateLockProvider(this.dataSource);
+	}
+
+	@Transactional(value = "defaultTransactionManager")
+	@Scheduled(fixedDelay = 60 * ONE_MINUTE, initialDelay = ONE_MINUTE)
+	@SchedulerLock(name = "clearExpiredSites", lockAtMostFor = "3590s", lockAtLeastFor = "3590s")
+	public void clearExpiredSites() {
+		try {
+			LockAssert.assertLocked();
+		} catch (IllegalArgumentException e) {
+			return;
+		}
+		long start = System.currentTimeMillis();
+		int count = this.customClearTasks.clearExpiredSites(TimeUnit.MINUTES.toMillis(15));
+		long execution = System.currentTimeMillis() - start;
+		log.info("clearExpiredSites took {}ms, deleted {} records", execution, count);
+	}
+
+	@Transactional(value = "defaultTransactionManager")
+	@Scheduled(fixedDelay = 60 * ONE_MINUTE, initialDelay = 12 * ONE_MINUTE)
+	@SchedulerLock(name = "clearExpiredTokens", lockAtMostFor = "3590s", lockAtLeastFor = "3590s")
+	public void clearExpiredTokens() {
+		try {
+			LockAssert.assertLocked();
+		} catch (IllegalArgumentException e) {
+			return;
+		}
+		long start = System.currentTimeMillis();
+		int count = this.customClearTasks.clearExpiredTokens(TimeUnit.MINUTES.toMillis(15));
+		long execution = System.currentTimeMillis() - start;
+		log.info("clearExpiredTokens took {}ms, deleted {} records", execution, count);
+	}
+
+	@Transactional(value = "defaultTransactionManager")
+	@Scheduled(fixedDelay = 60 * ONE_MINUTE, initialDelay = 24 * ONE_MINUTE)
+	@SchedulerLock(name = "clearExpiredAuthorizationCodes", lockAtMostFor = "3590s", lockAtLeastFor = "3590s")
+	public void clearExpiredAuthorizationCodes() {
+		try {
+			LockAssert.assertLocked();
+		} catch (IllegalArgumentException e) {
+			return;
+		}
+		long start = System.currentTimeMillis();
+		int count = this.customClearTasks.clearExpiredAuthorizationCodes(TimeUnit.MINUTES.toMillis(15));
+		long execution = System.currentTimeMillis() - start;
+		log.info("clearExpiredAuthorizationCodes took {}ms, deleted {} records", execution, count);
+	}
+
+	@Transactional(value = "defaultTransactionManager")
+	@Scheduled(fixedDelay = 60 * ONE_MINUTE, initialDelay = 36 * ONE_MINUTE)
+	@SchedulerLock(name = "clearExpiredDeviceCodes", lockAtMostFor = "3590s", lockAtLeastFor = "3590s")
+	public void clearExpiredDeviceCodes() {
+		try {
+			LockAssert.assertLocked();
+		} catch (IllegalArgumentException e) {
+			return;
+		}
+		long start = System.currentTimeMillis();
+		int count = this.customClearTasks.clearExpiredDeviceCodes(TimeUnit.MINUTES.toMillis(15));
+		long execution = System.currentTimeMillis() - start;
+		log.info("clearExpiredDeviceCodes took {}ms, deleted {} records", execution, count);
+	}
+
+	@Transactional(value = "defaultTransactionManager")
+	@Scheduled(fixedDelay = 60 * ONE_MINUTE, initialDelay = 48 * ONE_MINUTE)
+	@SchedulerLock(name = "clearExpiredAcrs", lockAtMostFor = "3590s", lockAtLeastFor = "3590s")
+	public void clearExpiredAcrs() {
+		try {
+			LockAssert.assertLocked();
+		} catch (IllegalArgumentException e) {
+			return;
+		}
+		long start = System.currentTimeMillis();
+		int count = this.customClearTasks.clearExpiredAcrs(TimeUnit.MINUTES.toMillis(15));
+		long execution = System.currentTimeMillis() - start;
+		log.info("clearExpiredAcrs took {}ms, deleted {} records", execution, count);
+	}
+
+	@Transactional(value = "defaultTransactionManager")
+	@Scheduled(fixedDelay = 60 * ONE_MINUTE, initialDelay = 48 * ONE_MINUTE)
+	@SchedulerLock(name = "clearExpiredDeviceAcrs", lockAtMostFor = "3590s", lockAtLeastFor = "3590s")
+	public void clearExpiredDeviceAcrs() {
+		try {
+			LockAssert.assertLocked();
+		} catch (IllegalArgumentException e) {
+			return;
+		}
+		long start = System.currentTimeMillis();
+		int count = this.customClearTasks.clearExpiredAcrs(TimeUnit.MINUTES.toMillis(15));
+		long execution = System.currentTimeMillis() - start;
+		log.info("clearExpiredDeviceAcrs took {}ms, deleted {} records", execution, count);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunAccessTokenEnhancer.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunAccessTokenEnhancer.java
new file mode 100644
index 000000000..ed49df033
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunAccessTokenEnhancer.java
@@ -0,0 +1,163 @@
+package cz.muni.ics.oidc.server;
+
+import static cz.muni.ics.oauth2.service.IntrospectionResultAssembler.SCOPE;
+import static cz.muni.ics.oauth2.service.IntrospectionResultAssembler.SCOPE_SEPARATOR;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import java.util.Date;
+import java.util.UUID;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.OIDCTokenService;
+import cz.muni.ics.openid.connect.service.UserInfoService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.security.oauth2.provider.OAuth2Request;
+import org.springframework.security.oauth2.provider.token.TokenEnhancer;
+
+/**
+ * Copy of ConnectTokenEnhancer.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunAccessTokenEnhancer implements TokenEnhancer {
+
+    private final static Logger log = LoggerFactory.getLogger(PerunAccessTokenEnhancer.class);
+
+    private final ConfigurationPropertiesBean configBean;
+    private final JWTSigningAndValidationService jwtService;
+    private final ClientDetailsEntityService clientService;
+    private final UserInfoService userInfoService;
+    private final OIDCTokenService connectTokenService;
+
+    private AccessTokenClaimsModifier accessTokenClaimsModifier;
+
+    @Autowired
+    public PerunAccessTokenEnhancer(ConfigurationPropertiesBean configBean,
+                                    JWTSigningAndValidationService jwtService,
+                                    ClientDetailsEntityService clientService,
+                                    UserInfoService userInfoService,
+                                    OIDCTokenService connectTokenService)
+    {
+        this.configBean = configBean;
+        this.jwtService = jwtService;
+        this.clientService = clientService;
+        this.userInfoService = userInfoService;
+        this.connectTokenService = connectTokenService;
+    }
+
+    public void setAccessTokenClaimsModifier(AccessTokenClaimsModifier accessTokenClaimsModifier) {
+        this.accessTokenClaimsModifier = accessTokenClaimsModifier;
+    }
+
+    /**
+     * Exact copy from ConnectTokenEnhancer with added hooks.
+     */
+    @Override
+    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
+        Date iat = new Date();
+        OAuth2AccessTokenEntity token = (OAuth2AccessTokenEntity) accessToken;
+        OAuth2Request originalAuthRequest = authentication.getOAuth2Request();
+
+        String clientId = originalAuthRequest.getClientId();
+        ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+
+        UserInfo userInfo = null;
+        if (originalAuthRequest.getScope().contains(SystemScopeService.OPENID_SCOPE)
+                && !authentication.isClientOnly()) {
+            userInfo = userInfoService.getByUsernameAndClientId(authentication.getName(), clientId);
+        }
+
+        // create signed access token
+        String sub = userInfo != null ? userInfo.getSub() : authentication.getName();
+        JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
+                .claim("azp", clientId)
+                .issuer(configBean.getIssuer())
+                .issueTime(iat)
+                .expirationTime(token.getExpiration())
+                .subject(sub)
+                .claim(SCOPE, Joiner.on(SCOPE_SEPARATOR).join(accessToken.getScope()))
+                .jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it
+        accessTokenClaimsHook(sub, builder, accessToken, authentication, userInfo);
+
+        String audience = (String) authentication.getOAuth2Request().getExtensions().get("aud");
+        if (!Strings.isNullOrEmpty(audience)) {
+            builder.audience(Lists.newArrayList(audience));
+        }
+
+        JWTClaimsSet claims = builder.build();
+
+        JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm();
+        JWSHeader header = new JWSHeader(signingAlg, JOSEObjectType.JWT, null, null, null, null, null, null, null, null,
+                jwtService.getDefaultSignerKeyId(), true, null, null);
+        SignedJWT signed = new SignedJWT(header, claims);
+
+        jwtService.signJwt(signed);
+        token.setJwt(signed);
+
+        if (userInfo != null) {
+            //needs access token
+            JWT idToken = connectTokenService.createIdToken(client, originalAuthRequest, iat, userInfo.getSub(), token);
+            // attach the id token to the parent access token
+            token.setIdToken(idToken);
+            if (log.isDebugEnabled()) log.debug("idToken: {}", idToken.serialize());
+        } else {
+            // can't create an id token if we can't find the user
+            log.warn("Request for ID token when no user is present.");
+        }
+
+        this.logHook(token, authentication);
+        return token;
+    }
+
+    private void logHook(OAuth2AccessTokenEntity token, OAuth2Authentication authentication) {
+        //log request info from authentication
+        Object principal = authentication.getPrincipal();
+        String userId = principal instanceof User ? ((User) principal).getUsername() : principal.toString();
+        OAuth2Request oAuth2Request = authentication.getOAuth2Request();
+        log.info("userId: {}, clientId: {}, grantType: {}, redirectUri: {}, scopes: {}",
+                userId, oAuth2Request.getClientId(), oAuth2Request.getGrantType(), oAuth2Request.getRedirectUri(), token.getScope());
+        if (log.isDebugEnabled()) log.debug("access token: {}", token.getValue());
+    }
+
+    private void accessTokenClaimsHook(String sub, JWTClaimsSet.Builder builder, OAuth2AccessToken accessToken,
+                                       OAuth2Authentication authentication, UserInfo userInfo)
+    {
+        if (accessTokenClaimsModifier != null) {
+            accessTokenClaimsModifier.modifyClaims(sub, builder, accessToken, authentication, userInfo);
+        }
+    }
+
+    @FunctionalInterface
+    public interface AccessTokenClaimsModifier {
+        void modifyClaims(String sub, JWTClaimsSet.Builder builder, OAuth2AccessToken accessToken,
+                          OAuth2Authentication authentication, UserInfo userInfo);
+    }
+
+    public static class NoOpAccessTokenClaimsModifier implements AccessTokenClaimsModifier {
+        @Override
+        public void modifyClaims(String sub, JWTClaimsSet.Builder builder, OAuth2AccessToken accessToken,
+                                 OAuth2Authentication authentication, UserInfo userInfo)
+        {
+            log.debug("no modification");
+        }
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunAcrRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunAcrRepository.java
new file mode 100644
index 000000000..5721bb477
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunAcrRepository.java
@@ -0,0 +1,81 @@
+package cz.muni.ics.oidc.server;
+
+import java.time.Instant;
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import cz.muni.ics.openid.connect.models.Acr;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Repository class for ACR model.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Repository
+@Transactional(value = "defaultTransactionManager")
+public class PerunAcrRepository {
+
+	@PersistenceContext(unitName = "defaultPersistenceUnit")
+	private EntityManager manager;
+
+	public Acr getActive(String sub, String clientId, String state) {
+		TypedQuery<Acr> query = manager.createNamedQuery(Acr.GET_ACTIVE, Acr.class);
+		query.setParameter(Acr.PARAM_SUB, sub);
+		query.setParameter(Acr.PARAM_CLIENT_ID, clientId);
+		query.setParameter(Acr.PARAM_STATE, state);
+		query.setParameter(Acr.PARAM_EXPIRES_AT, now());
+		try {
+			return query.getSingleResult();
+		} catch (NoResultException e) {
+			return null;
+		}
+	}
+
+	public Acr getById(Long id) {
+		TypedQuery<Acr> query = manager.createNamedQuery(Acr.GET_BY_ID, Acr.class);
+		query.setParameter(Acr.PARAM_ID, id);
+		query.setParameter(Acr.PARAM_EXPIRES_AT, now());
+
+		try {
+			return query.getSingleResult();
+		} catch (NoResultException e) {
+			return null;
+		}
+	}
+
+	@Transactional
+	public Acr store(Acr acr) {
+		Acr existing = getActive(acr.getSub(), acr.getClientId(), acr.getState());
+		if (existing != null) {
+			return existing;
+		} else {
+			Acr tmp = manager.merge(acr);
+			manager.flush();
+			return tmp;
+		}
+	}
+
+	@Transactional
+	public void remove(Long id) {
+		Acr acr = getById(id);
+		if (acr != null) {
+			manager.remove(acr);
+		}
+	}
+
+	@Transactional
+	public void deleteExpired() {
+		Query query = manager.createNamedQuery(Acr.DELETE_EXPIRED);
+		query.setParameter(Acr.PARAM_EXPIRES_AT, now());
+		query.executeUpdate();
+	}
+
+	private long now() {
+		return Instant.now().toEpochMilli();
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunDeviceCodeAcrRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunDeviceCodeAcrRepository.java
new file mode 100644
index 000000000..bcb2d05b8
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunDeviceCodeAcrRepository.java
@@ -0,0 +1,91 @@
+package cz.muni.ics.oidc.server;
+
+import java.time.Instant;
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import cz.muni.ics.openid.connect.models.Acr;
+import cz.muni.ics.openid.connect.models.DeviceCodeAcr;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Repository class for ACR model.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Repository
+@Transactional(value = "defaultTransactionManager")
+public class PerunDeviceCodeAcrRepository {
+
+	@PersistenceContext(unitName = "defaultPersistenceUnit")
+	private EntityManager manager;
+
+	public DeviceCodeAcr getActiveByDeviceCode(String deviceCode) {
+		TypedQuery<DeviceCodeAcr> query = manager.createNamedQuery(DeviceCodeAcr.GET_ACTIVE_BY_DEVICE_CODE,
+				DeviceCodeAcr.class);
+		query.setParameter(DeviceCodeAcr.PARAM_DEVICE_CODE, deviceCode);
+		query.setParameter(Acr.PARAM_EXPIRES_AT, now());
+		try {
+			return query.getSingleResult();
+		} catch (NoResultException e) {
+			return null;
+		}
+	}
+
+	public DeviceCodeAcr getByUserCode(String userCode) {
+		TypedQuery<DeviceCodeAcr> query = manager.createNamedQuery(DeviceCodeAcr.GET_BY_USER_CODE, DeviceCodeAcr.class);
+		query.setParameter(DeviceCodeAcr.PARAM_USER_CODE, userCode);
+
+		try {
+			return query.getSingleResult();
+		} catch (NoResultException e) {
+			return null;
+		}
+	}
+
+	public DeviceCodeAcr getById(Long id) {
+		TypedQuery<DeviceCodeAcr> query = manager.createNamedQuery(DeviceCodeAcr.GET_BY_ID, DeviceCodeAcr.class);
+		query.setParameter(DeviceCodeAcr.PARAM_ID, id);
+		query.setParameter(DeviceCodeAcr.PARAM_EXPIRES_AT, now());
+
+		try {
+			return query.getSingleResult();
+		} catch (NoResultException e) {
+			return null;
+		}
+	}
+
+	@Transactional
+	public DeviceCodeAcr store(DeviceCodeAcr acr) {
+		try {
+			return getActiveByDeviceCode(acr.getDeviceCode());
+		} catch (NoResultException e) {
+			DeviceCodeAcr tmp = manager.merge(acr);
+			manager.flush();
+			return tmp;
+		}
+	}
+
+	@Transactional
+	public void remove(Long id) {
+		DeviceCodeAcr acr = getById(id);
+		if (acr != null) {
+			manager.remove(acr);
+		}
+	}
+
+	@Transactional
+	public void deleteExpired() {
+		Query query = manager.createNamedQuery(DeviceCodeAcr.DELETE_EXPIRED);
+		query.setParameter(DeviceCodeAcr.PARAM_EXPIRES_AT, now());
+		query.executeUpdate();
+	}
+
+	private long now() {
+		return Instant.now().toEpochMilli();
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunIntrospectionResultAssembler.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunIntrospectionResultAssembler.java
new file mode 100644
index 000000000..b132fe64c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunIntrospectionResultAssembler.java
@@ -0,0 +1,65 @@
+package cz.muni.ics.oidc.server;
+
+import com.google.common.collect.Sets;
+import com.google.gson.JsonElement;
+import java.util.Map;
+import java.util.Set;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.service.impl.DefaultIntrospectionResultAssembler;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Assembler of result obtained from introspection endpoint.
+ * Adds "iss" to identify issuer in response from Introspection endpoint to Resource Server.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunIntrospectionResultAssembler extends DefaultIntrospectionResultAssembler {
+
+	private final static Logger log = LoggerFactory.getLogger(PerunIntrospectionResultAssembler.class);
+
+	private final ConfigurationPropertiesBean configBean;
+	private final ScopeClaimTranslationService translator;
+
+	public PerunIntrospectionResultAssembler(ConfigurationPropertiesBean configBean,
+											 ScopeClaimTranslationService translator)
+	{
+		this.configBean = configBean;
+		this.translator = translator;
+	}
+
+	@Override
+	public Map<String, Object> assembleFrom(OAuth2AccessTokenEntity accessToken, UserInfo userInfo,
+											Set<String> resourceServerScopes)
+	{
+		log.info("adding claims at Introspection endpoint for client {}({}) and user {}({})",
+				accessToken.getClient().getClientId(), accessToken.getClient().getClientName(), userInfo.getSub(),
+				userInfo.getName());
+		Map<String, Object> map = super.assembleFrom(accessToken, userInfo, resourceServerScopes);
+		Set<String> accessTokenScopes = accessToken.getScope();
+		Set<String> scopes = Sets.intersection(resourceServerScopes, accessTokenScopes);
+		log.debug("resource server scopes: {}", resourceServerScopes);
+		log.debug("access token scopes   : {}", accessTokenScopes);
+		log.debug("common scopes         : {}", scopes);
+		this.addDataToResponse(map, userInfo, scopes);
+		return map;
+	}
+
+	private void addDataToResponse(Map<String, Object> map, UserInfo userInfo, Set<String> scopes) {
+		log.debug("adding iss to introspection response {}", map);
+		map.put("iss", configBean.getIssuer());
+		log.debug("adding user claims");
+		Set<String> authorizedClaims = translator.getClaimsForScopeSet(scopes);
+		for (Map.Entry<String, JsonElement> claim : userInfo.toJson().entrySet()) {
+			if (authorizedClaims.contains(claim.getKey()) && claim.getValue() != null && !claim.getValue().isJsonNull()) {
+				log.debug("adding claim {} with value {}", claim.getKey(), claim.getValue());
+				map.put(claim.getKey(), claim.getValue());
+			}
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunOIDCTokenService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunOIDCTokenService.java
new file mode 100644
index 000000000..44fda4266
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunOIDCTokenService.java
@@ -0,0 +1,124 @@
+package cz.muni.ics.oidc.server;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.nimbusds.jose.util.JSONObjectUtils;
+import com.nimbusds.jwt.JWTClaimsSet;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import java.text.ParseException;
+import java.util.Map;
+import java.util.Set;
+import net.minidev.json.JSONArray;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
+import cz.muni.ics.openid.connect.service.UserInfoService;
+import cz.muni.ics.openid.connect.service.impl.DefaultOIDCTokenService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.oauth2.provider.OAuth2Request;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+/**
+ * Modifies ID Token.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunOIDCTokenService extends DefaultOIDCTokenService {
+
+	private static final Logger log = LoggerFactory.getLogger(PerunOIDCTokenService.class);
+
+	public static final String SESSION_PARAM_ACR = "acr";
+
+	private final UserInfoService userInfoService;
+	private final ScopeClaimTranslationService translator;
+	private final PerunOidcConfig perunOidcConfig;
+
+	private final Gson gson = new Gson();
+
+	@Autowired
+	public PerunOIDCTokenService(UserInfoService userInfoService,
+								 ScopeClaimTranslationService translator,
+								 PerunOidcConfig perunOidcConfig)
+	{
+		this.userInfoService = userInfoService;
+		this.translator = translator;
+		this.perunOidcConfig = perunOidcConfig;
+	}
+
+	@Override
+	protected void addCustomIdTokenClaims(JWTClaimsSet.Builder idClaims, ClientDetailsEntity client, OAuth2Request request,
+										  String sub, OAuth2AccessTokenEntity accessToken)
+	{
+		log.debug("modifying ID token");
+		String userId = accessToken.getAuthenticationHolder().getAuthentication().getName();
+		String clientId = request.getClientId();
+		log.debug("userId={},clientId={}", userId, clientId);
+
+		Set<String> scopes = accessToken.getScope();
+		Set<String> authorizedClaims = translator.getClaimsForScopeSet(scopes);
+		Set<String> idTokenClaims = translator.getClaimsForScopeSet(perunOidcConfig.getIdTokenScopes());
+
+		for (Map.Entry<String, JsonElement> claim : userInfoService.getByUsernameAndClientId(userId,
+				clientId).toJson().entrySet()) {
+			String claimKey = claim.getKey();
+			JsonElement claimValue = claim.getValue();
+			if (claimValue != null && !claimValue.isJsonNull() && authorizedClaims.contains(claimKey)
+					&& idTokenClaims.contains(claimKey))
+			{
+				log.debug("adding to ID token claim {} with value {}", claimKey, claimValue);
+				idClaims.claim(claimKey, gson2jsonsmart(claimValue));
+			}
+		}
+
+		String acr = getAuthnContextClass();
+		if (acr != null) {
+			log.debug("adding to ID token claim acr with value {}", acr);
+			idClaims.claim("acr", acr);
+		}
+	}
+
+	private String getAuthnContextClass() {
+		ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
+		return (String) attr.getAttribute(SESSION_PARAM_ACR, RequestAttributes.SCOPE_SESSION);
+	}
+
+	/**
+	 * Converts claim values from com.google.gson.JsonElement to net.minidev.json.JSONObject or primitive value
+	 *
+	 * @param jsonElement Gson representation
+	 * @return json-smart representation
+	 */
+	private Object gson2jsonsmart(JsonElement jsonElement) {
+		if (jsonElement.isJsonPrimitive()) {
+			JsonPrimitive p = jsonElement.getAsJsonPrimitive();
+			if (p.isString()) {
+				return p.getAsString();
+			} else if (p.isBoolean()) {
+				return p.getAsBoolean();
+			} else if (p.isNumber()) {
+				return p.getAsNumber();
+			} else {
+				log.warn("unknown JsonPrimitive {}", p);
+				return null;
+			}
+		} else if (jsonElement.isJsonObject()) {
+			try {
+				return JSONObjectUtils.parse(gson.toJson(jsonElement));
+			} catch (ParseException e) {
+				log.error("cannot convert Gson->smart-json.JSONObject", e);
+				return null;
+			}
+		} else if (jsonElement.isJsonArray()) {
+			JSONArray jsonArray = new JSONArray();
+			jsonElement.getAsJsonArray().forEach(je -> jsonArray.appendElement(gson2jsonsmart(je)));
+			return jsonArray;
+		} else {
+			return null;
+		}
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunPrincipal.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunPrincipal.java
new file mode 100644
index 000000000..03574f498
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunPrincipal.java
@@ -0,0 +1,35 @@
+package cz.muni.ics.oidc.server;
+
+/**
+ * Principal specific for Perun user. User is identified by login (extLogin) and name
+ * of the external source (extSourceName) he/she used for login (usually identity provider).
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunPrincipal {
+
+	private final String extLogin;
+	private final String extSourceName;
+
+	public PerunPrincipal(String extLogin, String extSourceName) {
+		this.extLogin = extLogin;
+		this.extSourceName = extSourceName;
+	}
+
+	public String getExtLogin() {
+		return extLogin;
+	}
+
+	public String getExtSourceName() {
+		return extSourceName;
+	}
+
+	@Override
+	public String toString() {
+		return "PerunPrincipal{" +
+				"extLogin='" + extLogin + '\'' +
+				", extSourceName='" + extSourceName + '\'' +
+				'}';
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunScopeClaimTranslationService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunScopeClaimTranslationService.java
new file mode 100644
index 000000000..4a65c6961
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/PerunScopeClaimTranslationService.java
@@ -0,0 +1,88 @@
+package cz.muni.ics.oidc.server;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.SetMultimap;
+import cz.muni.ics.oidc.server.claims.PerunCustomClaimDefinition;
+import cz.muni.ics.oidc.server.userInfo.PerunUserInfoService;
+import java.util.HashSet;
+import java.util.Set;
+import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Translates scopes to claims. A single scope can provide access to multiple claims.
+ * Set this as spring bean named "scopeClaimTranslator". This code is copied from class
+ * cz.muni.ics.openid.connect.service.impl.DefaultScopeClaimTranslationService
+ * which for some reason is not accessible in this project, and extended.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunScopeClaimTranslationService implements ScopeClaimTranslationService {
+
+	private final static Logger log = LoggerFactory.getLogger(PerunScopeClaimTranslationService.class);
+
+	public static final String OPENID = "openid";
+	public static final String PROFILE = "profile";
+	public static final String EMAIL = "email";
+	public static final String PHONE = "phone";
+	public static final String ADDRESS = "address";
+
+	private final SetMultimap<String, String> scopesToClaims = HashMultimap.create();
+
+	public void setPerunUserInfoService(PerunUserInfoService perunUserInfoService) {
+		for(PerunCustomClaimDefinition pccd : perunUserInfoService.getCustomClaims()) {
+			log.info("adding custom claim \"{}\" in scope \"{}\" ",pccd.getClaim(),pccd.getScope());
+			scopesToClaims.put(pccd.getScope(),pccd.getClaim());
+		}
+	}
+
+	/**
+	 * Default constructor; initializes scopesToClaims map
+	 */
+	public PerunScopeClaimTranslationService() {
+		scopesToClaims.put(OPENID, "sub");
+
+		scopesToClaims.put(PROFILE, "name");
+		scopesToClaims.put(PROFILE, "preferred_username");
+		scopesToClaims.put(PROFILE, "given_name");
+		scopesToClaims.put(PROFILE, "family_name");
+		scopesToClaims.put(PROFILE, "middle_name");
+		scopesToClaims.put(PROFILE, "nickname");
+		scopesToClaims.put(PROFILE, "profile");
+		scopesToClaims.put(PROFILE, "picture");
+		scopesToClaims.put(PROFILE, "website");
+		scopesToClaims.put(PROFILE, "gender");
+		scopesToClaims.put(PROFILE, "zoneinfo");
+		scopesToClaims.put(PROFILE, "locale");
+		scopesToClaims.put(PROFILE, "updated_at");
+		scopesToClaims.put(PROFILE, "birthdate");
+
+		scopesToClaims.put(EMAIL, "email");
+		scopesToClaims.put(EMAIL, "email_verified");
+
+		scopesToClaims.put(PHONE, "phone_number");
+		scopesToClaims.put(PHONE, "phone_number_verified");
+
+		scopesToClaims.put(ADDRESS, "address");
+	}
+
+	@Override
+	public Set<String> getClaimsForScope(String scope) {
+		if (scopesToClaims.containsKey(scope)) {
+			return scopesToClaims.get(scope);
+		} else {
+			return new HashSet<>();
+		}
+	}
+
+	@Override
+	public Set<String> getClaimsForScopeSet(Set<String> scopes) {
+		Set<String> result = new HashSet<>();
+		for (String scope : scopes) {
+			result.addAll(getClaimsForScope(scope));
+		}
+		return result;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapter.java
new file mode 100644
index 000000000..817333820
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapter.java
@@ -0,0 +1,94 @@
+package cz.muni.ics.oidc.server.adapters;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Interface for getting data from Perun interfaces.
+ * Used for fetching necessary data about users, services etc.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ * @author Dominik František Bučík <bucik@ics.muni.cz>
+ * @author Peter Jancus <jancus@ics.muni.cz>
+ */
+public abstract class PerunAdapter implements PerunAdapterMethods {
+
+	private PerunAdapterMethods adapterPrimary;
+	private PerunAdapterMethods adapterFallback;
+	private PerunAdapterMethodsRpc adapterRpc;
+	private PerunAdapterMethodsLdap adapterLdap;
+
+	private boolean callFallback;
+
+	public PerunAdapterMethods getAdapterPrimary() {
+		return adapterPrimary;
+	}
+
+	public void setAdapterPrimary(PerunAdapterMethods adapterPrimary) {
+		this.adapterPrimary = adapterPrimary;
+	}
+
+	public PerunAdapterMethods getAdapterFallback() {
+		return adapterFallback;
+	}
+
+	public void setAdapterFallback(PerunAdapterMethods adapterFallback) {
+		this.adapterFallback = adapterFallback;
+	}
+
+	public PerunAdapterMethodsRpc getAdapterRpc() {
+		return adapterRpc;
+	}
+
+	public void setAdapterRpc(PerunAdapterMethodsRpc adapterRpc) {
+		this.adapterRpc = adapterRpc;
+	}
+
+	public PerunAdapterMethodsLdap getAdapterLdap() {
+		return adapterLdap;
+	}
+
+	public void setAdapterLdap(PerunAdapterMethodsLdap adapterLdap) {
+		this.adapterLdap = adapterLdap;
+	}
+
+	public boolean isCallFallback() {
+		return callFallback;
+	}
+
+	public void setCallFallback(boolean callFallback) {
+		this.callFallback = callFallback;
+	}
+
+	public static boolean decideAccess(Set<Long> foundVoIds, Set<Long> foundGroupIds, Set<Long> mandatoryVos,
+									   Set<Long> mandatoryGroups, Set<Long> envVos, Set<Long> envGroups) {
+		boolean res = true;
+		if (!mandatoryVos.isEmpty()) {
+			res = !Collections.disjoint(foundVoIds, mandatoryVos);
+		}
+		if (!mandatoryGroups.isEmpty()) {
+			res = res && !Collections.disjoint(foundGroupIds, mandatoryGroups);
+		}
+		if (!envVos.isEmpty()) {
+			res = res && !Collections.disjoint(foundVoIds, envVos);
+		}
+		if (!envGroups.isEmpty()) {
+			res = res && !Collections.disjoint(foundGroupIds, envGroups);
+		}
+
+		return res;
+	}
+
+	public static boolean decideAccess(Set<Long> foundVoIds, Set<Long> foundGroupIds, Set<Long> vos, Set<Long> groups) {
+		boolean res = true;
+		if (!vos.isEmpty()) {
+			res = !Collections.disjoint(foundVoIds, vos);
+		}
+		if (!groups.isEmpty()) {
+			res = res && !Collections.disjoint(foundGroupIds, groups);
+		}
+
+		return res;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethods.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethods.java
new file mode 100644
index 000000000..6ff6560b0
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethods.java
@@ -0,0 +1,343 @@
+package cz.muni.ics.oidc.server.adapters;
+
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.models.Resource;
+import cz.muni.ics.oidc.models.Vo;
+import cz.muni.ics.oidc.server.PerunPrincipal;
+import cz.muni.ics.oidc.server.connectors.Affiliation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for getting data from Perun interfaces.
+ * Used for fetching necessary data about users, services etc.
+ *
+ * @author Martin Kuba makub@ics.muni.czc
+ * @author Dominik František Bučík bucik@ics.muni.cz
+ * @author Peter Jancus jancus@ics.muni.cz
+ */
+public interface PerunAdapterMethods {
+
+	/**
+	 * Fetch user based on his principal (extLogin and extSource) from Perun
+	 *
+	 * @param perunPrincipal principal of user
+	 * @return PerunUser with id of found user
+	 */
+	PerunUser getPreauthenticatedUserId(PerunPrincipal perunPrincipal);
+
+	/**
+	 * Fetch user attribute values
+	 *
+	 * @param user User for whom the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getUserAttributeValues(PerunUser user, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch user attribute values
+	 *
+	 * @param userId Id of the user for whom the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getUserAttributeValues(Long userId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch user attribute value
+	 *
+	 * @param user User for whom the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getUserAttributeValue(PerunUser user, String attrToFetch);
+
+	/**
+	 * Fetch user attribute value
+	 *
+	 * @param userId Id of user for whom the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getUserAttributeValue(Long userId, String attrToFetch);
+
+	/**
+	 * Fetch facility registered in Perun associated with the given OIDC client_id value.
+	 *
+	 * @param clientId value for the OIDCClientID attribute
+	 * @return Found facility or null
+	 */
+	Facility getFacilityByClientId(String clientId);
+
+	/**
+	 * Decide if facility has requested to check membership of user in associated groups before access
+	 *
+	 * @param facility Facility object
+	 * @return TRUE if check should be done, FALSE otherwise
+	 */
+	boolean isMembershipCheckEnabledOnFacility(Facility facility);
+
+	/**
+	 * Perform check if user can access service based on his/her membership
+	 * in groups assigned to facility resources
+	 *
+	 * @param facility Facility object
+	 * @param userId ID of user
+	 * @return TRUE if user can access, FALSE otherwise
+	 */
+	boolean canUserAccessBasedOnMembership(Facility facility, Long userId);
+
+	/**
+	 * Fetch facility attribute values
+	 *
+	 * @param facility Facility for which the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getFacilityAttributeValues(Facility facility, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch facility attribute values
+	 *
+	 * @param facilityId Id of facility for which the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getFacilityAttributeValues(Long facilityId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch facility attribute value
+	 *
+	 * @param facility Facility for which the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getFacilityAttributeValue(Facility facility, String attrToFetch);
+
+	/**
+	 * Fetch facility attribute value
+	 *
+	 * @param facilityId Id of facility for which the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getFacilityAttributeValue(Long facilityId, String attrToFetch);
+
+	/**
+	 * Check if user is member of the group
+	 * @param userId ID of user
+	 * @param groupId ID of group
+	 * @return TRUE if the user is member of the group, FALSE otherwise
+	 */
+	boolean isUserInGroup(Long userId, Long groupId);
+
+	/**
+	 * For the given user, get all string values of the groupAffiliation attribute of groups of the user
+	 *
+	 * @param userId id of user
+	 * @param groupAffiliationsAttr name of attribute containing group affiliations
+	 * @return List of values of the affiliation attribute (filled or empty)
+	 */
+	List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr);
+
+	/**
+	 * For the given facility, get all allowed groups
+	 *
+	 * @param facility facility
+	 * @return List of unique names of the groups (filled or empty)
+	 */
+	List<String> getGroupsAssignedToResourcesWithUniqueNames(Facility facility);
+
+	/**
+	 * Get groups where user is active (also in VO in which group exists) and are assigned to the resources of facility.
+	 * Fill the uniqueGroupName for groups as well.
+	 * @param facilityId Id of Facility
+	 * @param userId Id of User
+	 * @return Set of groups (filled or empty)
+	 */
+	Set<Group> getGroupsWhereUserIsActiveWithUniqueNames(Long facilityId, Long userId);
+
+	/**
+	 * Fetch VO attribute values
+	 *
+	 * @param vo VO for which the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getVoAttributeValues(Vo vo, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch VO attribute values
+	 *
+	 * @param voId Id of VO for which the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getVoAttributeValues(Long voId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch VO attribute value
+	 *
+	 * @param vo Vo for which the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getVoAttributeValue(Vo vo, String attrToFetch);
+
+	/**
+	 * Fetch VO attribute value
+	 *
+	 * @param voId Id of vo for which the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getVoAttributeValue(Long voId, String attrToFetch);
+
+	/**
+	 * Get vo with the given short name
+	 *
+	 * @param shortName short name of VO
+	 * @return Found VO or null
+	 */
+	Vo getVoByShortName(String shortName);
+
+	/**
+	 * Fetch group attribute values
+	 *
+	 * @param group Group for which the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getGroupAttributeValues(Group group, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch group attribute values
+	 *
+	 * @param groupId Id of group for which the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getGroupAttributeValues(Long groupId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch group attribute value
+	 *
+	 * @param group Group for which the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getGroupAttributeValue(Group group, String attrToFetch);
+
+	/**
+	 * Fetch group attribute value
+	 *
+	 * @param groupId Id of group for which the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getGroupAttributeValue(Long groupId, String attrToFetch);
+
+	/**
+	 * Gets the capabilities
+	 * @param facility Facility representing client
+	 * @param groupNames Names of groups the user is member of.
+	 * @param facilityCapabilitiesAttrName String name of attribute containing facility capabilities. Pass null for ignore.
+	 * @param resourceCapabilitiesAttrName String name of attribute containing resource capabilities. Pass null for ignore.
+	 * @return set of capabilities assigned on resources
+	 */
+	Set<String> getCapabilities(Facility facility, Set<String> groupNames, String facilityCapabilitiesAttrName,
+								String resourceCapabilitiesAttrName);
+
+	/**
+	 * Gets the capabilities
+	 * @param facility Facility representing client
+	 * @param idToGnameMap Map of ID to name of the groups user is member of.
+	 * @param facilityCapabilitiesAttrName String name of attribute containing facility capabilities. Pass null for ignore.
+	 * @param resourceCapabilitiesAttrName String name of attribute containing resource capabilities. Pass null for ignore.
+	 * @return set of capabilities assigned on resources
+	 */
+	Set<String> getCapabilities(Facility facility, Map<Long, String> idToGnameMap, String facilityCapabilitiesAttrName,
+								String resourceCapabilitiesAttrName);
+
+	/**
+	 * Fetch resource attribute values
+	 *
+	 * @param resource Resource for which the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getResourceAttributeValues(Resource resource, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch resource attribute values
+	 *
+	 * @param resourceId Id of resource for which the attribute values are being fetch
+	 * @param attrsToFetch List of Strings representing attribute values to fetch
+	 * @return Map of attrName:PerunAttributeValue (filled or empty)
+	 */
+	Map<String, PerunAttributeValue> getResourceAttributeValues(Long resourceId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch resource attribute value
+	 *
+	 * @param resource Resource for which the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getResourceAttributeValue(Resource resource, String attrToFetch);
+
+	/**
+	 * Fetch resource attribute value
+	 *
+	 * @param resourceId Id of resource for which the attribute value is being fetch
+	 * @param attrToFetch String representing attribute value to fetch
+	 * @return PerunAttributeValue or null if not found
+	 */
+	PerunAttributeValue getResourceAttributeValue(Long resourceId, String attrToFetch);
+
+	/**
+	 * Fetch group IDs where user is member based on userID and voID
+	 * @param userId id of user
+	 * @param voId id of vo
+	 * @return List of groups IDs (filled or empty)
+	 */
+	Set<Long> getUserGroupsIds(Long userId, Long voId);
+
+	/**
+	 * Check if user is valid member of given VOs (identified by IDs)
+	 * @param userId ID of user in Perun
+	 * @param mandatoryVos Set of IDs identifying the VOs
+	 * @param mandatoryGroups Set of IDs identifying the Groups
+	 * @param envVos Set of IDs identifying the VOs
+	 * @param envGroups Set of IDs identifying the Groups
+	 * @return returns TRUE if:
+	 * 	User is member of at least one specified mandatory VO, and
+	 * 	User is member of at least one specified mandatory GROUP, and
+	 * 	User is member of at least one specified env VO, and
+	 * 	User is member of at least one specified env GROUP.
+	 * 	Returns FALSE otherwise.
+	 */
+	boolean isValidMemberInGroupsAndVos(Long userId, Set<Long> mandatoryVos, Set<Long> mandatoryGroups,
+										Set<Long> envVos, Set<Long> envGroups);
+
+	/**
+	 * Check if user is valid member of given VOs (identified by IDs)
+	 * @param userId ID of user in Perun
+	 * @param vos Set of IDs identifying the VOs
+	 * @param groups Set of IDs identifying the Groups
+	 * @return returns TRUE if:
+	 * 	User is member of at least one specified mandatory VO, and
+	 * 	User is member of at least one specified mandatory GROUP, and
+	 * 	Returns FALSE otherwise.
+	 */
+	boolean isValidMemberInGroupsAndVos(Long userId, Set<Long> vos, Set<Long> groups);
+
+	boolean isUserInVo(Long userId, String voShortName);
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethodsLdap.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethodsLdap.java
new file mode 100644
index 000000000..6a4834128
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethodsLdap.java
@@ -0,0 +1,9 @@
+package cz.muni.ics.oidc.server.adapters;
+
+/**
+ * Interface with specific methods that only LDAP interface can execute
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public interface PerunAdapterMethodsLdap {
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethodsRpc.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethodsRpc.java
new file mode 100644
index 000000000..46926a3ad
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/PerunAdapterMethodsRpc.java
@@ -0,0 +1,231 @@
+package cz.muni.ics.oidc.server.adapters;
+
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.models.PerunAttribute;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.models.Resource;
+import cz.muni.ics.oidc.models.Vo;
+import cz.muni.ics.oidc.server.connectors.Affiliation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Interface with specific methods that only rpc interface can execute
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public interface PerunAdapterMethodsRpc {
+
+	/**
+	 * Get list of groups where user can register to gain access to the service
+	 *
+	 * @param facility facility the user tries to access
+	 * @param userId id of user
+	 * @return List of groups where user can register or empty list
+	 */
+	Map<Vo, List<Group>> getGroupsForRegistration(Facility facility, Long userId, List<String> voShortNames);
+
+	/**
+	 * Decide if there is a group where user can register
+	 *
+	 * @param facility facility being accessed
+	 * @return true if at least one group with registration form exists
+	 */
+	boolean groupWhereCanRegisterExists(Facility facility);
+
+	/**
+	 * Sets the attribute of the user.
+	 * @param userId id of user
+	 * @param attribute attribute
+	 */
+	boolean setUserAttribute(Long userId, PerunAttribute attribute);
+
+	/**
+	 * For the given user, gets all string values of the affiliation attribute of all UserExtSources of type ExtSourceIdp
+	 * @param userId id of user
+	 * @return list of values of attribute affiliation
+	 */
+	List<Affiliation> getUserExtSourcesAffiliations(Long userId);
+
+	/**
+	 * Gets the map of entityless attributes.
+	 * @param attributeName full name of attribute
+	 * @return map of attributes
+	 */
+	Map<String, PerunAttribute> getEntitylessAttributes(String attributeName);
+
+	/**
+	 * Fetch facility attributes
+	 * @param facility Facility for which the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getFacilityAttributes(Facility facility, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch facility attributes
+	 * @param facilityId Id of facility for which the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getFacilityAttributes(Long facilityId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch group attributes
+	 * @param group Group for which the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getGroupAttributes(Group group, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch facility attribute value
+	 *
+	 * @param facility Facility for which the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getFacilityAttribute(Facility facility, String attrToFetch);
+
+	/**
+	 * Fetch facility attribute value
+	 *
+	 * @param facilityId Id of facility for which the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getFacilityAttribute(Long facilityId, String attrToFetch);
+
+	/**
+	 * Fetch group attributes
+	 * @param groupId Id of group for which the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getGroupAttributes(Long groupId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch group attribute value
+	 *
+	 * @param group Group for which the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getGroupAttribute(Group group, String attrToFetch);
+
+	/**
+	 * Fetch group attribute value
+	 *
+	 * @param groupId Id of group for which the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getGroupAttribute(Long groupId, String attrToFetch);
+
+	/**
+	 * Fetch user attributes
+	 * @param user User for whom the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getUserAttributes(PerunUser user, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch user attributes
+	 * @param userId Id of user for whom the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getUserAttributes(Long userId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch user attribute value
+	 *
+	 * @param user User for whom the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getUserAttribute(PerunUser user, String attrToFetch);
+
+	/**
+	 * Fetch user attribute value
+	 *
+	 * @param userId Id of user for whom the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getUserAttribute(Long userId, String attrToFetch);
+
+	/**
+	 * Fetch vo attributes
+	 * @param vo VO for which the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getVoAttributes(Vo vo, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch vo attributes
+	 * @param voId Id of VO for which the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getVoAttributes(Long voId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch VO attribute
+	 *
+	 * @param vo Vo for which the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getVoAttribute(Vo vo, String attrToFetch);
+
+	/**
+	 * Fetch VO attribute
+	 *
+	 * @param voId Id of vo for which the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getVoAttribute(Long voId, String attrToFetch);
+
+	/**
+	 * Fetch resource attributes
+	 * @param resource VO for which the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getResourceAttributes(Resource resource, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch resource attributes
+	 * @param resourceId Id of VO for which the attribute values are being fetched.
+	 * @param attrsToFetch Collection of String representing attribute values to fetch
+	 * @return Map of attrName:PerunAttribute (filled or empty)
+	 */
+	Map<String, PerunAttribute> getResourceAttributes(Long resourceId, Collection<String> attrsToFetch);
+
+	/**
+	 * Fetch VO attribute
+	 *
+	 * @param resource Resource for which the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getResourceAttribute(Resource resource, String attrToFetch);
+
+	/**
+	 * Fetch VO attribute
+	 *
+	 * @param resourceId Id of resource for which the attribute is being fetched
+	 * @param attrToFetch String representing attribute to fetch
+	 * @return PerunAttribute
+	 */
+	PerunAttribute getResourceAttribute(Long resourceId, String attrToFetch);
+
+	boolean hasApplicationForm(String voShortName);
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterImpl.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterImpl.java
new file mode 100644
index 000000000..93492df00
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterImpl.java
@@ -0,0 +1,404 @@
+package cz.muni.ics.oidc.server.adapters.impl;
+
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.models.Resource;
+import cz.muni.ics.oidc.models.Vo;
+import cz.muni.ics.oidc.server.PerunPrincipal;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.connectors.Affiliation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Basic adapter. This one should be used across the application to call the methods that are common
+ * among all adapters. Otherwise use secific adapter.
+ *
+ * @author Dominik František Bučík <bucik@ics.muni.cz>
+ */
+public class PerunAdapterImpl extends PerunAdapter {
+
+    @Override
+    public PerunUser getPreauthenticatedUserId(PerunPrincipal perunPrincipal) {
+        try {
+            return this.getAdapterPrimary().getPreauthenticatedUserId(perunPrincipal);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getPreauthenticatedUserId(perunPrincipal);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Facility getFacilityByClientId(String clientId) {
+        try {
+            return this.getAdapterPrimary().getFacilityByClientId(clientId);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getFacilityByClientId(clientId);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public boolean isMembershipCheckEnabledOnFacility(Facility facility) {
+        try {
+            return this.getAdapterPrimary().isMembershipCheckEnabledOnFacility(facility);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().isMembershipCheckEnabledOnFacility(facility);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public boolean canUserAccessBasedOnMembership(Facility facility, Long userId) {
+        try {
+            return this.getAdapterPrimary().canUserAccessBasedOnMembership(facility, userId);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().canUserAccessBasedOnMembership(facility, userId);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+
+    @Override
+    public boolean isUserInGroup(Long userId, Long groupId) {
+        try {
+            return this.getAdapterPrimary().isUserInGroup(userId, groupId);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().isUserInGroup(userId, groupId);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr) {
+        try {
+            return this.getAdapterPrimary().getGroupAffiliations(userId, groupAffiliationsAttr);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getGroupAffiliations(userId, groupAffiliationsAttr);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public List<String> getGroupsAssignedToResourcesWithUniqueNames(Facility facility) {
+        try {
+            return this.getAdapterPrimary().getGroupsAssignedToResourcesWithUniqueNames(facility);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getGroupsAssignedToResourcesWithUniqueNames(facility);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Vo getVoByShortName(String shortName) {
+        try {
+            return this.getAdapterPrimary().getVoByShortName(shortName);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getVoByShortName(shortName);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getUserAttributeValues(PerunUser user, Collection<String> attrsToFetch) {
+        return this.getUserAttributeValues(user.getId(), attrsToFetch);
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getUserAttributeValues(Long userId, Collection<String> attrsToFetch) {
+        try {
+            return this.getAdapterPrimary().getUserAttributeValues(userId, attrsToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getUserAttributeValues(userId, attrsToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public PerunAttributeValue getUserAttributeValue(PerunUser user, String attrToFetch) {
+        return this.getUserAttributeValue(user.getId(), attrToFetch);
+    }
+
+    @Override
+    public PerunAttributeValue getUserAttributeValue(Long userId, String attrToFetch) {
+        try {
+            return this.getAdapterPrimary().getUserAttributeValue(userId, attrToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getUserAttributeValue(userId, attrToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getFacilityAttributeValues(Facility facility, Collection<String> attrsToFetch) {
+        return this.getFacilityAttributeValues(facility.getId(), attrsToFetch);
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getFacilityAttributeValues(Long facilityId, Collection<String> attrsToFetch) {
+        try {
+            return this.getAdapterPrimary().getFacilityAttributeValues(facilityId, attrsToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getFacilityAttributeValues(facilityId, attrsToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public PerunAttributeValue getFacilityAttributeValue(Facility facility, String attrToFetch) {
+        return this.getFacilityAttributeValue(facility.getId(), attrToFetch);
+    }
+
+    @Override
+    public PerunAttributeValue getFacilityAttributeValue(Long facilityId, String attrToFetch) {
+        try {
+            return this.getAdapterPrimary().getFacilityAttributeValue(facilityId, attrToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getFacilityAttributeValue(facilityId, attrToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getVoAttributeValues(Vo vo, Collection<String> attrsToFetch) {
+        return this.getVoAttributeValues(vo.getId(), attrsToFetch);
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getVoAttributeValues(Long voId, Collection<String> attrsToFetch) {
+        try {
+            return this.getAdapterPrimary().getVoAttributeValues(voId, attrsToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getVoAttributeValues(voId, attrsToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public PerunAttributeValue getVoAttributeValue(Vo vo, String attrToFetch) {
+        return this.getVoAttributeValue(vo.getId(), attrToFetch);
+    }
+
+    @Override
+    public PerunAttributeValue getVoAttributeValue(Long voId, String attrToFetch) {
+        try {
+            return this.getAdapterPrimary().getVoAttributeValue(voId, attrToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getVoAttributeValue(voId, attrToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getGroupAttributeValues(Group group, Collection<String> attrsToFetch) {
+        return this.getGroupAttributeValues(group.getId(), attrsToFetch);
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getGroupAttributeValues(Long groupId, Collection<String> attrsToFetch) {
+        try {
+            return this.getAdapterPrimary().getGroupAttributeValues(groupId, attrsToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getGroupAttributeValues(groupId, attrsToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public PerunAttributeValue getGroupAttributeValue(Group group, String attrToFetch) {
+        return this.getGroupAttributeValue(group.getId(), attrToFetch);
+    }
+
+    @Override
+    public PerunAttributeValue getGroupAttributeValue(Long groupId, String attrToFetch) {
+        try {
+            return this.getAdapterPrimary().getGroupAttributeValue(groupId, attrToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getGroupAttributeValue(groupId, attrToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getResourceAttributeValues(Resource resource, Collection<String> attrsToFetch) {
+        return this.getResourceAttributeValues(resource.getId(), attrsToFetch);
+    }
+
+    @Override
+    public Map<String, PerunAttributeValue> getResourceAttributeValues(Long resourceId, Collection<String> attrsToFetch) {
+        try {
+            return this.getAdapterPrimary().getResourceAttributeValues(resourceId, attrsToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getResourceAttributeValues(resourceId, attrsToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public PerunAttributeValue getResourceAttributeValue(Resource resource, String attrToFetch) {
+        return this.getResourceAttributeValue(resource.getId(), attrToFetch);
+    }
+
+    @Override
+    public PerunAttributeValue getResourceAttributeValue(Long resourceId, String attrToFetch) {
+        try {
+            return this.getAdapterPrimary().getResourceAttributeValue(resourceId, attrToFetch);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getResourceAttributeValue(resourceId, attrToFetch);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Set<String> getCapabilities(Facility facility, Set<String> groupNames, String facilityCapabilitiesAttrName, String resourceCapabilitiesAttrName) {
+        try {
+            return this.getAdapterPrimary().getCapabilities(facility, groupNames, facilityCapabilitiesAttrName, resourceCapabilitiesAttrName);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getCapabilities(facility, groupNames, facilityCapabilitiesAttrName, resourceCapabilitiesAttrName);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Set<String> getCapabilities(Facility facility, Map<Long, String> idToGnameMap, String facilityCapabilitiesAttrName, String resourceCapabilitiesAttrName) {
+        try {
+            return this.getAdapterPrimary().getCapabilities(facility, idToGnameMap, facilityCapabilitiesAttrName, resourceCapabilitiesAttrName);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getCapabilities(facility, idToGnameMap, facilityCapabilitiesAttrName, resourceCapabilitiesAttrName);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Set<Group> getGroupsWhereUserIsActiveWithUniqueNames(Long facilityId, Long userId) {
+        try {
+            return this.getAdapterPrimary().getGroupsWhereUserIsActiveWithUniqueNames(facilityId, userId);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getGroupsWhereUserIsActiveWithUniqueNames(facilityId, userId);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public Set<Long> getUserGroupsIds(Long userId, Long voId) {
+        try {
+            return this.getAdapterPrimary().getUserGroupsIds(userId, voId);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().getUserGroupsIds(userId, voId);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public boolean isValidMemberInGroupsAndVos(Long userId, Set<Long> mandatoryVos, Set<Long> mandatoryGroups,
+                                               Set<Long> envVos, Set<Long> envGroups) {
+        try {
+            return this.getAdapterPrimary().isValidMemberInGroupsAndVos(userId, mandatoryVos, mandatoryGroups,
+                    envVos, envGroups);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().isValidMemberInGroupsAndVos(userId, mandatoryVos, mandatoryGroups,
+                        envVos, envGroups);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public boolean isValidMemberInGroupsAndVos(Long userId, Set<Long> vos, Set<Long> groups) {
+        try {
+            return this.getAdapterPrimary().isValidMemberInGroupsAndVos(userId, vos, groups);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().isValidMemberInGroupsAndVos(userId, vos, groups);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public boolean isUserInVo(Long userId, String voShortName) {
+        try {
+            return this.getAdapterPrimary().isUserInVo(userId, voShortName);
+        } catch (UnsupportedOperationException e) {
+            if (this.isCallFallback()) {
+                return this.getAdapterFallback().isUserInVo(userId, voShortName);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterLdap.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterLdap.java
new file mode 100644
index 000000000..752cdf4fa
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterLdap.java
@@ -0,0 +1,867 @@
+package cz.muni.ics.oidc.server.adapters.impl;
+
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.ASSIGNED_GROUP_ID;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.CN;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.DESCRIPTION;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.EDU_PERSON_PRINCIPAL_NAMES;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.GIVEN_NAME;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.MEMBER_OF;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.O;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.OBJECT_CLASS;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.OU_PEOPLE;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_FACILITY;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_FACILITY_DN;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_FACILITY_ID;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_GROUP;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_GROUP_ID;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_PARENT_GROUP_ID;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_RESOURCE;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_RESOURCE_ID;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_UNIQUE_GROUP_NAME;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_USER;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_USER_ID;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_VO;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.PERUN_VO_ID;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.SN;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.UNIQUE_MEMBER;
+import static cz.muni.ics.oidc.server.adapters.impl.PerunAdapterLdapConstants.UUID;
+import static org.apache.directory.ldap.client.api.search.FilterBuilder.and;
+import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
+import static org.apache.directory.ldap.client.api.search.FilterBuilder.or;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import cz.muni.ics.oidc.exceptions.InconvertibleValueException;
+import cz.muni.ics.oidc.models.AttributeMapping;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.models.Resource;
+import cz.muni.ics.oidc.models.Vo;
+import cz.muni.ics.oidc.models.enums.PerunAttrValueType;
+import cz.muni.ics.oidc.models.enums.PerunEntityType;
+import cz.muni.ics.oidc.server.PerunPrincipal;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.adapters.PerunAdapterMethods;
+import cz.muni.ics.oidc.server.adapters.PerunAdapterMethodsLdap;
+import cz.muni.ics.oidc.server.connectors.Affiliation;
+import cz.muni.ics.oidc.server.connectors.PerunConnectorLdap;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.directory.api.ldap.model.entry.Attribute;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.entry.Value;
+import org.apache.directory.api.ldap.model.message.SearchScope;
+import org.apache.directory.ldap.client.api.search.FilterBuilder;
+import org.apache.directory.ldap.client.template.EntryMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * Connects to Perun using LDAP.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ * @author Martin Kuba makub@ics.muni.cz
+ */
+public class PerunAdapterLdap extends PerunAdapterWithMappingServices implements PerunAdapterMethods, PerunAdapterMethodsLdap {
+
+	private final static Logger log = LoggerFactory.getLogger(PerunAdapterLdap.class);
+
+	private PerunConnectorLdap connectorLdap;
+	private String oidcClientIdAttr;
+	private String oidcCheckMembershipAttr;
+	private final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
+
+	public void setConnectorLdap(PerunConnectorLdap connectorLdap) {
+		this.connectorLdap = connectorLdap;
+	}
+
+	public void setOidcClientIdAttr(String oidcClientIdAttr) {
+		this.oidcClientIdAttr = oidcClientIdAttr;
+	}
+
+	public void setOidcCheckMembershipAttr(String oidcCheckMembershipAttr) {
+		this.oidcCheckMembershipAttr = oidcCheckMembershipAttr;
+	}
+
+	/**
+	 * Fetch user based on his principal (extLogin and extSource) from Perun
+	 *
+	 * @param perunPrincipal principal of user
+	 * @return PerunUser with id of found user
+	 */
+	@Override
+	public PerunUser getPreauthenticatedUserId(PerunPrincipal perunPrincipal) {
+		FilterBuilder filter = and(
+				equal(OBJECT_CLASS, PERUN_USER), equal(EDU_PERSON_PRINCIPAL_NAMES, perunPrincipal.getExtLogin())
+		);
+		SearchScope scope = SearchScope.ONELEVEL;
+		String[] attributes = new String[]{PERUN_USER_ID, GIVEN_NAME, SN};
+		EntryMapper<PerunUser> mapper = e -> {
+			if (!checkHasAttributes(e, new String[] { PERUN_USER_ID, SN })) {
+				return null;
+			}
+
+			long id = Long.parseLong(e.get(PERUN_USER_ID).getString());
+			String firstName = (e.get(GIVEN_NAME) != null) ? e.get(GIVEN_NAME).getString() : null;
+			String lastName = e.get(SN).getString();
+			return new PerunUser(id, firstName, lastName);
+		};
+
+		return connectorLdap.searchFirst(OU_PEOPLE, filter, scope, attributes, mapper);
+	}
+
+	@Override
+	public Facility getFacilityByClientId(String clientId) {
+		if (!StringUtils.hasText(clientId)) {
+			return null;
+		}
+		SearchScope scope = SearchScope.ONELEVEL;
+		String[] attributes = new String[]{PERUN_FACILITY_ID, DESCRIPTION, CN};
+		EntryMapper<Facility> mapper = e -> {
+			if (!checkHasAttributes(e, attributes)) {
+				return null;
+			}
+
+			long id = Long.parseLong(e.get(PERUN_FACILITY_ID).getString());
+			String name = e.get(CN).getString();
+			String description = e.get(DESCRIPTION).getString();
+
+			return new Facility(id, name, description);
+		};
+
+		AttributeMapping mapping = this.getFacilityAttributesMappingService().getMappingByIdentifier(oidcClientIdAttr);
+
+		FilterBuilder filter = and(equal(OBJECT_CLASS, PERUN_FACILITY), equal(mapping.getLdapName(), clientId));
+		return connectorLdap.searchFirst(null, filter, scope, attributes, mapper);
+	}
+
+	@Override
+	public boolean isMembershipCheckEnabledOnFacility(Facility facility) {
+		boolean res = false;
+
+		PerunAttributeValue attrVal = getFacilityAttributeValue(facility, oidcCheckMembershipAttr);
+		if (attrVal != null && !attrVal.isNullValue()) {
+			res = attrVal.valueAsBoolean();
+		}
+
+		return res;
+	}
+
+	@Override
+	public boolean canUserAccessBasedOnMembership(Facility facility, Long userId) {
+		Set<Long> groupsWithAccessIds = getGroupIdsWithAccessToFacility(facility.getId());
+		if (groupsWithAccessIds == null || groupsWithAccessIds.isEmpty()) {
+			return false;
+		}
+
+		Set<Long> userGroupIds = getGroupIdsWhereUserIsMember(userId, null);
+		if (userGroupIds == null || userGroupIds.isEmpty()) {
+			return false;
+		}
+
+		return !Collections.disjoint(userGroupIds, groupsWithAccessIds);
+	}
+
+	@Override
+	public boolean isUserInGroup(Long userId, Long groupId) {
+		String uniqueMemberValue = PERUN_USER_ID + '=' + userId + ',' + OU_PEOPLE + ',' + connectorLdap.getBaseDN();
+		FilterBuilder filter = and(
+				equal(OBJECT_CLASS, PERUN_GROUP),
+				equal(PERUN_GROUP_ID, String.valueOf(groupId)),
+				equal(UNIQUE_MEMBER, uniqueMemberValue)
+		);
+
+		EntryMapper<Long> mapper = e -> Long.parseLong(e.get(PERUN_GROUP_ID).getString());
+
+		String[] attributes = new String[] { PERUN_GROUP_ID };
+
+		List<Long> ids = connectorLdap.search(null, filter, SearchScope.SUBTREE, attributes, mapper);
+		return ids.stream().filter(groupId::equals).count() == 1L;
+	}
+
+	@Override
+	public List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr) {
+		Set<Long> userGroupIds = getGroupIdsWhereUserIsMember(userId, null);
+		if (userGroupIds == null || userGroupIds.isEmpty()) {
+			return new ArrayList<>();
+		}
+
+		FilterBuilder[] groupIdFilters = new FilterBuilder[userGroupIds.size()];
+		int i = 0;
+		for (Long id: userGroupIds) {
+			groupIdFilters[i++] = equal(PERUN_GROUP_ID, String.valueOf(id));
+		}
+
+		AttributeMapping affiliationsMapping = getGroupAttributesMappingService().getMappingByIdentifier(groupAffiliationsAttr);
+
+		FilterBuilder filterBuilder = and(equal(OBJECT_CLASS, PERUN_GROUP), or(groupIdFilters));
+		String[] attributes = new String[] { affiliationsMapping.getLdapName() };
+		EntryMapper<Set<Affiliation>> mapper = e -> {
+			Set<Affiliation> affiliations = new HashSet<>();
+			if (!checkHasAttributes(e, attributes)) {
+				return affiliations;
+			}
+
+			Attribute a = e.get(affiliationsMapping.getLdapName());
+			long linuxTime = System.currentTimeMillis() / 1000L;
+			a.iterator().forEachRemaining(v -> affiliations.add(new Affiliation(null, v.getString(), linuxTime)));
+
+			return affiliations;
+		};
+
+		List<Set<Affiliation>> affiliationSets = connectorLdap.search(null, filterBuilder, SearchScope.SUBTREE, attributes, mapper);
+
+		return affiliationSets.stream().flatMap(Set::stream).distinct().collect(Collectors.toList());
+	}
+
+	@Override
+	public List<String> getGroupsAssignedToResourcesWithUniqueNames(Facility facility) {
+		List<String> res = new ArrayList<>();
+
+		Set<Long> groupIds = getGroupIdsWithAccessToFacility(facility.getId());
+		if (groupIds == null || groupIds.isEmpty()) {
+			return res;
+		}
+
+		FilterBuilder[] partialFilters = new FilterBuilder[groupIds.size()];
+		int i = 0;
+		for (Long id: groupIds) {
+			partialFilters[i++] = equal(PERUN_GROUP_ID, String.valueOf(id));
+		}
+
+		FilterBuilder filter = and(equal(OBJECT_CLASS, PERUN_GROUP), or(partialFilters));
+		String[] attributes = new String[] {PERUN_UNIQUE_GROUP_NAME};
+		EntryMapper<String> mapper = e -> {
+			if (!checkHasAttributes(e, attributes)) {
+				return null;
+			}
+
+			return e.get(PERUN_UNIQUE_GROUP_NAME).getString();
+		};
+
+		List<String> uniqueGroupNames = connectorLdap.search(null, filter, SearchScope.SUBTREE, attributes, mapper);
+		uniqueGroupNames = uniqueGroupNames.stream().filter(Objects::nonNull).collect(Collectors.toList());
+		return uniqueGroupNames;
+	}
+
+	@Override
+	public Vo getVoByShortName(String shortName) {
+		FilterBuilder filter = and(equal(OBJECT_CLASS, PERUN_VO), equal(O, shortName));
+		String[] attributes = new String[] { PERUN_VO_ID, O, DESCRIPTION };
+		EntryMapper<Vo> mapper = e -> {
+			if (!checkHasAttributes(e, attributes)) {
+				return null;
+			}
+
+			Long id = Long.valueOf(e.get(PERUN_VO_ID).getString());
+			String shortNameVo = e.get(O).getString();
+			String name = e.get(DESCRIPTION).getString();
+
+			return new Vo(id, name, shortNameVo);
+		};
+
+		return connectorLdap.searchFirst(null, filter, SearchScope.ONELEVEL, attributes, mapper);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getUserAttributeValues(PerunUser user, Collection<String> attrsToFetch) {
+		return this.getUserAttributeValues(user.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getUserAttributeValues(Long userId, Collection<String> attrsToFetch) {
+		String dnPrefix = PERUN_USER_ID + '=' + userId + ',' + OU_PEOPLE;
+		return getAttributeValues(dnPrefix, attrsToFetch, PerunEntityType.USER);
+	}
+
+	@Override
+	public PerunAttributeValue getUserAttributeValue(PerunUser user, String attrToFetch) {
+		return this.getUserAttributeValue(user.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getUserAttributeValue(Long userId, String attrToFetch) {
+		Map<String, PerunAttributeValue> map = this.getUserAttributeValues(
+				userId, Collections.singletonList(attrToFetch));
+		return map.getOrDefault(attrToFetch, null);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getFacilityAttributeValues(Facility facility, Collection<String> attrsToFetch) {
+		return this.getFacilityAttributeValues(facility.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getFacilityAttributeValues(Long facilityId, Collection<String> attrsToFetch) {
+		String dnPrefix = PERUN_FACILITY_ID + '=' + facilityId;
+		return getAttributeValues(dnPrefix, attrsToFetch, PerunEntityType.FACILITY);
+	}
+
+	@Override
+	public PerunAttributeValue getFacilityAttributeValue(Facility facility, String attrToFetch) {
+		return this.getFacilityAttributeValue(facility.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getFacilityAttributeValue(Long facilityId, String attrToFetch) {
+		Map<String, PerunAttributeValue> map = this.getFacilityAttributeValues(
+				facilityId, Collections.singletonList(attrToFetch));
+		return map.getOrDefault(attrToFetch, null);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getVoAttributeValues(Vo vo, Collection<String> attrsToFetch) {
+		return this.getVoAttributeValues(vo.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getVoAttributeValues(Long voId, Collection<String> attrsToFetch) {
+		String dnPrefix = PERUN_VO_ID + '=' + voId;
+		return getAttributeValues(dnPrefix, attrsToFetch, PerunEntityType.VO);
+	}
+
+	@Override
+	public PerunAttributeValue getVoAttributeValue(Vo vo, String attrToFetch) {
+		return this.getVoAttributeValue(vo.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getVoAttributeValue(Long voId, String attrToFetch) {
+		Map<String, PerunAttributeValue> map = this.getVoAttributeValues(
+				voId, Collections.singletonList(attrToFetch));
+		return map.getOrDefault(attrToFetch, null);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getGroupAttributeValues(Group group, Collection<String> attrsToFetch) {
+		return this.getGroupAttributeValues(group.getVoId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getGroupAttributeValues(Long groupId, Collection<String> attrsToFetch) {
+		String dnPrefix = PERUN_GROUP_ID + '=' + groupId;
+		return getAttributeValues(dnPrefix, attrsToFetch, PerunEntityType.GROUP);
+	}
+
+	@Override
+	public PerunAttributeValue getGroupAttributeValue(Group group, String attrToFetch) {
+		return this.getGroupAttributeValue(group.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getGroupAttributeValue(Long groupId, String attrToFetch) {
+		Map<String, PerunAttributeValue> map = this.getGroupAttributeValues(
+				groupId, Collections.singletonList(attrToFetch));
+		return map.getOrDefault(attrToFetch, null);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getResourceAttributeValues(Resource resource, Collection<String> attrsToFetch) {
+		return this.getResourceAttributeValues(resource.getVoId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getResourceAttributeValues(Long resourceId, Collection<String> attrsToFetch) {
+		String dnPrefix = PERUN_RESOURCE_ID + '=' + resourceId;
+		return getAttributeValues(dnPrefix, attrsToFetch, PerunEntityType.RESOURCE);
+	}
+
+	@Override
+	public PerunAttributeValue getResourceAttributeValue(Resource resource, String attrToFetch) {
+		return this.getResourceAttributeValue(resource.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getResourceAttributeValue(Long resourceId, String attrToFetch) {
+		Map<String, PerunAttributeValue> map = this.getResourceAttributeValues(
+				resourceId, Collections.singletonList(attrToFetch));
+		return map.getOrDefault(attrToFetch, null);
+	}
+
+	@Override
+	public Set<String> getCapabilities(Facility facility, Set<String> groupNames,
+									   String facilityCapabilitiesAttrName,
+									   String resourceCapabilitiesAttrName)
+	{
+		if (facility == null) {
+			return new HashSet<>();
+		} else if (groupNames == null || groupNames.isEmpty()) {
+			return new HashSet<>();
+		}
+
+		Set<Long> groupIdsFromGNames = getGroupsByUniqueGroupNames(groupNames).stream()
+				.map(Group::getId).collect(Collectors.toSet());
+
+		FilterBuilder[] parts = new FilterBuilder[groupIdsFromGNames.size()];
+		int i = 0;
+		for (Long gid : groupIdsFromGNames) {
+			parts[i] = equal(ASSIGNED_GROUP_ID, String.valueOf(gid));
+			i++;
+		}
+		return getCapabilities(facility, facilityCapabilitiesAttrName, resourceCapabilitiesAttrName, parts);
+	}
+
+	@Override
+	public Set<String> getCapabilities(Facility facility, Map<Long, String> idToGnameMap,
+									   String facilityCapabilitiesAttrName, String resourceCapabilitiesAttrName)
+	{
+		if (facility == null) {
+			return new HashSet<>();
+		} else if (idToGnameMap == null || idToGnameMap.isEmpty()) {
+			return new HashSet<>();
+		}
+
+		FilterBuilder[] parts = new FilterBuilder[idToGnameMap.size()];
+		int i = 0;
+		for (Long gid : idToGnameMap.keySet()) {
+			parts[i] = equal(ASSIGNED_GROUP_ID, String.valueOf(gid));
+			i++;
+		}
+		return getCapabilities(facility, facilityCapabilitiesAttrName, resourceCapabilitiesAttrName, parts);
+	}
+
+	@Override
+	public Set<Group> getGroupsWhereUserIsActiveWithUniqueNames(Long facilityId, Long userId) {
+		Set<Long> userGroups = this.getGroupIdsWhereUserIsMember(userId, null);
+		Set<Long> facilityGroups = this.getGroupIdsWithAccessToFacility(facilityId);
+		Set<Long> groupIds = userGroups.stream()
+				.filter(facilityGroups::contains)
+				.collect(Collectors.toSet());
+		log.debug("Intersection of userGroups and facilityGroups: {}", groupIds);
+		Set<Group> groups = new HashSet<>();
+
+		if (groupIds.isEmpty()) {
+			return groups;
+		}
+
+		List<Group> resGroups = getGroups(groupIds, PERUN_GROUP_ID);
+		groups = new HashSet<>(resGroups);
+
+		return groups;
+	}
+
+	@Override
+	public Set<Long> getUserGroupsIds(Long userId, Long voId) {
+		return getGroupIdsWhereUserIsMember(userId, voId);
+	}
+
+	@Override
+	public boolean isValidMemberInGroupsAndVos(Long userId, Set<Long> mandatoryVos, Set<Long> mandatoryGroups,
+											   Set<Long> envVos, Set<Long> envGroups) {
+		final Set<Long> foundGroupIds = new HashSet<>();
+		final Set<Long> foundVoIds = new HashSet<>();
+		String dnPrefix = getDnPrefixForUserId(userId);
+		String[] attributes = new String[] { MEMBER_OF };
+		EntryMapper<Void> mapper = e -> {
+			if (checkHasAttributes(e, attributes)) {
+				Attribute a = e.get(MEMBER_OF);
+				a.iterator().forEachRemaining(id -> {
+					String fullVal = id.getString();
+					String[] parts = fullVal.split(",", 3);
+
+					String groupId = parts[0];
+					groupId = groupId.replace(PERUN_GROUP_ID + '=', "");
+					foundGroupIds.add(Long.parseLong(groupId));
+					String voIdStr = parts[1];
+					voIdStr = voIdStr.replace(PERUN_VO_ID + '=', "");
+					foundVoIds.add(Long.parseLong(voIdStr));
+				});
+			}
+			return null;
+		};
+		connectorLdap.lookup(dnPrefix, attributes, mapper);
+
+		return PerunAdapter.decideAccess(foundVoIds, foundGroupIds, mandatoryVos, mandatoryGroups, envVos, envGroups);
+	}
+
+	@Override
+	public boolean isValidMemberInGroupsAndVos(Long userId, Set<Long> vos, Set<Long> groups) {
+		final Set<Long> foundGroupIds = new HashSet<>();
+		final Set<Long> foundVoIds = new HashSet<>();
+		String dnPrefix = getDnPrefixForUserId(userId);
+		String[] attributes = new String[] { MEMBER_OF };
+		EntryMapper<Void> mapper = e -> {
+			if (checkHasAttributes(e, attributes)) {
+				Attribute a = e.get(MEMBER_OF);
+				a.iterator().forEachRemaining(id -> {
+					String fullVal = id.getString();
+					String[] parts = fullVal.split(",", 3);
+
+					String groupId = parts[0];
+					groupId = groupId.replace(PERUN_GROUP_ID + '=', "");
+					foundGroupIds.add(Long.parseLong(groupId));
+					String voIdStr = parts[1];
+					voIdStr = voIdStr.replace(PERUN_VO_ID + '=', "");
+					foundVoIds.add(Long.parseLong(voIdStr));
+				});
+			}
+			return null;
+		};
+		connectorLdap.lookup(dnPrefix, attributes, mapper);
+
+		return PerunAdapter.decideAccess(foundVoIds, foundGroupIds, vos, groups);
+	}
+
+	@Override
+	public boolean isUserInVo(Long userId, String voShortName) {
+		if (userId == null) {
+			throw new IllegalArgumentException("No userId");
+		} else if (!StringUtils.hasText(voShortName)) {
+			throw new IllegalArgumentException("No voShortName");
+		}
+
+		String uniqueMember = getDnPrefixForUserId(userId) + ',' + this.connectorLdap.getBaseDN();
+		FilterBuilder filter = and(equal(OBJECT_CLASS, PERUN_VO), equal(UNIQUE_MEMBER, uniqueMember), equal(O, voShortName));
+		String[] attributes = new String[] { PERUN_VO_ID, O, DESCRIPTION };
+		EntryMapper<Vo> mapper = e -> {
+			if (!checkHasAttributes(e, attributes)) {
+				return null;
+			}
+
+			Long id = Long.valueOf(e.get(PERUN_VO_ID).getString());
+			String shortNameVo = e.get(O).getString();
+			String name = e.get(DESCRIPTION).getString();
+
+			return new Vo(id, name, shortNameVo);
+		};
+
+		Vo vo = connectorLdap.searchFirst(null, filter, SearchScope.ONELEVEL, attributes, mapper);
+		return vo != null;
+	}
+
+	private List<Group> getGroups(Collection<?> objects, String objectAttribute) {
+		List<Group> result;
+		if (objects == null || objects.size() <= 0) {
+			result = new ArrayList<>();
+		} else {
+			FilterBuilder filter;
+			if (objects.size() == 1) {
+				Object first = objects.toArray()[0];
+				filter = and(equal(OBJECT_CLASS, PERUN_GROUP), equal(objectAttribute, String.valueOf(first)));
+			} else {
+				FilterBuilder[] partialFilters = new FilterBuilder[objects.size()];
+				int i = 0;
+				for (Object obj: objects) {
+					partialFilters[i++] = equal(objectAttribute, String.valueOf(obj));
+				}
+				filter = and(equal(OBJECT_CLASS, PERUN_GROUP), or(partialFilters));
+			}
+
+			String[] attributes = new String[]{PERUN_GROUP_ID, CN, DESCRIPTION, PERUN_UNIQUE_GROUP_NAME,
+					PERUN_VO_ID, PERUN_PARENT_GROUP_ID, UUID};
+
+			EntryMapper<Group> mapper = e -> {
+				if (!checkHasAttributes(e, new String[]{
+						PERUN_GROUP_ID, CN, DESCRIPTION, PERUN_UNIQUE_GROUP_NAME, PERUN_VO_ID, UUID }))
+				{
+					return null;
+				}
+
+				Long id = Long.valueOf(e.get(PERUN_GROUP_ID).getString());
+				String name = e.get(CN).getString();
+				String description = e.get(DESCRIPTION).getString();
+				String uniqueName = e.get(PERUN_UNIQUE_GROUP_NAME).getString();
+				Long voId = Long.valueOf(e.get(PERUN_VO_ID).getString());
+				Long parentGroupId = null;
+				if (e.get(PERUN_PARENT_GROUP_ID) != null) {
+					parentGroupId = Long.valueOf(e.get(PERUN_PARENT_GROUP_ID).getString());
+				}
+				String uuid = e.get(UUID).getString();
+
+				return new Group(id, parentGroupId, name, description, uniqueName,uuid, voId);
+			};
+
+			result = connectorLdap.search(null, filter, SearchScope.SUBTREE, attributes, mapper);
+			result = result.stream().filter(Objects::nonNull).collect(Collectors.toList());
+		}
+
+		return result;
+	}
+
+	private Set<Long> getGroupIdsWhereUserIsMember(Long userId, Long voId) {
+		String dnPrefix = getDnPrefixForUserId(userId);
+		String[] attributes = new String[] { MEMBER_OF };
+		EntryMapper<Set<Long>> mapper = e -> {
+			Set<Long> ids = new HashSet<>();
+			if (checkHasAttributes(e, attributes)) {
+				Attribute a = e.get(MEMBER_OF);
+				a.iterator().forEachRemaining(id -> {
+					String fullVal = id.getString();
+					String[] parts = fullVal.split(",", 3);
+
+					String groupId = parts[0];
+					groupId = groupId.replace(PERUN_GROUP_ID + '=', "");
+
+					String voIdStr = parts[1];
+					voIdStr = voIdStr.replace(PERUN_VO_ID + '=', "");
+
+					if (voId == null || voId.equals(Long.parseLong(voIdStr))) {
+						ids.add(Long.parseLong(groupId));
+					}
+				});
+			}
+
+			return ids;
+		};
+
+		return connectorLdap.lookup(dnPrefix, attributes, mapper);
+	}
+
+	private String getDnPrefixForUserId(Long userId) {
+		return PERUN_USER_ID + '=' + userId + ',' + OU_PEOPLE;
+	}
+
+	private Set<Long> getGroupIdsWithAccessToFacility(Long facilityId) {
+		FilterBuilder filter = and(equal(OBJECT_CLASS, PERUN_RESOURCE), equal(PERUN_FACILITY_ID, String.valueOf(facilityId)));
+		String[] attributes = new String[] { ASSIGNED_GROUP_ID };
+		EntryMapper<Set<Long>> mapper = e -> {
+			Set<Long> ids = new HashSet<>();
+			if (checkHasAttributes(e, attributes)) {
+				Attribute a = e.get(ASSIGNED_GROUP_ID);
+				if (a != null) {
+					a.iterator().forEachRemaining(id -> ids.add(Long.valueOf(id.getString())));
+				}
+			}
+
+			return ids;
+		};
+
+		List<Set<Long>> assignedGroupIdsAll = connectorLdap.search(null, filter, SearchScope.SUBTREE, attributes, mapper);
+		return assignedGroupIdsAll.stream()
+				.flatMap(Set::stream)
+				.collect(Collectors.toSet());
+	}
+
+	private Map<String, PerunAttributeValue> getAttributeValues(String dnPrefix, Collection<String> attrsToFetch,
+																PerunEntityType entity) {
+		Set<AttributeMapping> mappings = this.getMappingsForAttrNames(entity, attrsToFetch);
+		String[] attributes = this.getAttributesFromMappings(mappings);
+
+		Map<String, PerunAttributeValue> res = new HashMap<>();
+		if (attributes.length != 0) {
+			EntryMapper<Map<String, PerunAttributeValue>> mapper = attrValueMapper(mappings);
+			res = this.connectorLdap.lookup(dnPrefix, attributes, mapper);
+		}
+
+		return res;
+	}
+
+	private List<Group> getGroupsByUniqueGroupNames(Set<String> groupNames) {
+		List<Group> groups = getGroups(groupNames, PERUN_UNIQUE_GROUP_NAME);
+		groups = groups.stream().filter(Objects::nonNull).collect(Collectors.toList());
+
+		return groups;
+	}
+
+	private boolean checkHasAttributes(Entry e, String[] attributes) {
+		if (e == null) {
+			return false;
+		} else if (attributes == null) {
+			return true;
+		}
+
+		for (String attr: attributes) {
+			if (e.get(attr) == null) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	private EntryMapper<Map<String, PerunAttributeValue>> attrValueMapper(Set<AttributeMapping> attrMappings) {
+		return entry -> {
+			Map<String, PerunAttributeValue> resultMap = new LinkedHashMap<>();
+			Map<String, Attribute> attrNamesMap = new HashMap<>();
+
+			for (Attribute attr : entry.getAttributes()) {
+				if (attr.isHumanReadable()) {
+					attrNamesMap.put(attr.getId(), attr);
+				}
+			}
+
+			for (AttributeMapping mapping: attrMappings) {
+				if (mapping.getLdapName() == null || mapping.getLdapName().isEmpty()) {
+					continue;
+				}
+				String ldapAttrName = mapping.getLdapName();
+				// the library always converts name of attribute to lowercase, therefore we need to convert it as well
+				Attribute attribute = attrNamesMap.getOrDefault(ldapAttrName.toLowerCase(), null);
+				PerunAttributeValue value = parseValue(attribute, mapping);
+				resultMap.put(mapping.getIdentifier(), value);
+			}
+
+			return resultMap;
+		};
+	}
+
+	private PerunAttributeValue parseValue(Attribute attr, AttributeMapping mapping) {
+		PerunAttrValueType type = mapping.getAttrType();
+		boolean isNull = (attr == null || attr.get() == null || attr.get().isNull());
+		switch (type) {
+			case STRING:
+				return new PerunAttributeValue(mapping.getIdentifier(), PerunAttributeValue.STRING_TYPE,
+						isNull ? jsonNodeFactory.nullNode() : jsonNodeFactory.textNode(attr.get().getString()));
+			case INTEGER:
+				return new PerunAttributeValue(mapping.getIdentifier(), PerunAttributeValue.INTEGER_TYPE,
+						isNull ? jsonNodeFactory.nullNode() : jsonNodeFactory.numberNode(Long.parseLong(attr.get().getString())));
+			case BOOLEAN:
+				return new PerunAttributeValue(mapping.getIdentifier(), PerunAttributeValue.BOOLEAN_TYPE,
+						isNull ? jsonNodeFactory.booleanNode(false) : jsonNodeFactory.booleanNode(Boolean.parseBoolean(attr.get().getString())));
+			case ARRAY:
+				return new PerunAttributeValue(mapping.getIdentifier(), PerunAttributeValue.ARRAY_TYPE,
+						isNull ? jsonNodeFactory.arrayNode() : getArrNode(attr));
+			case MAP_JSON:
+				return new PerunAttributeValue(mapping.getIdentifier(), PerunAttributeValue.MAP_TYPE,
+						isNull ? jsonNodeFactory.objectNode() : getMapNodeJson(attr));
+			case MAP_KEY_VALUE:
+				return new PerunAttributeValue(mapping.getIdentifier(), PerunAttributeValue.MAP_TYPE,
+						isNull ? jsonNodeFactory.objectNode() : getMapNodeSeparator(attr, mapping.getSeparator()));
+			default:
+				throw new IllegalArgumentException("unrecognized type");
+		}
+
+	}
+
+	private ObjectNode getMapNodeSeparator(Attribute attr, String separator) {
+		ObjectNode objectNode = jsonNodeFactory.objectNode();
+		for (Value value : attr) {
+			if (value.getString() != null) {
+				String[] parts = value.getString().split(separator, 2);
+				objectNode.put(parts[0], parts[1]);
+			}
+		}
+		return objectNode;
+	}
+
+	private ObjectNode getMapNodeJson(Attribute attr) {
+		String jsonStr = attr.get().getString();
+		ObjectMapper objectMapper = new ObjectMapper();
+		try {
+			return objectMapper.readValue(jsonStr, ObjectNode.class);
+		} catch (IOException e) {
+			throw new InconvertibleValueException("Could not parse value");
+		}
+	}
+
+	private ArrayNode getArrNode(Attribute attr) {
+		ArrayNode arrayNode = jsonNodeFactory.arrayNode(attr.size());
+		for (Value value : attr) {
+			arrayNode.add(value.getString());
+		}
+		return arrayNode;
+	}
+
+	private boolean isNumber(String s) {
+		try {
+			Long.parseLong(s);
+			return true;
+		} catch (NumberFormatException | NullPointerException e) {
+			return false;
+		}
+	}
+
+	private Set<AttributeMapping> getMappingsForAttrNames(PerunEntityType entity, Collection<String> attrsToFetch) {
+		Set<AttributeMapping> mappings;
+		switch (entity) {
+			case USER:
+				mappings = this.getUserAttributesMappingService()
+						.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			case FACILITY:
+				mappings = this.getFacilityAttributesMappingService()
+						.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			case VO:
+				mappings = this.getVoAttributesMappingService()
+						.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			case GROUP:
+				mappings = this.getGroupAttributesMappingService()
+						.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			case RESOURCE:
+				mappings = this.getResourceAttributesMappingService()
+						.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			default:
+				log.error("Unknown ENTITY {}", entity);
+				mappings = new HashSet<>();
+				break;
+		}
+
+		return mappings;
+	}
+
+	private String[] getAttributesFromMappings(Set<AttributeMapping> mappings) {
+		return mappings.stream()
+				.map(AttributeMapping::getLdapName)
+				.distinct()
+				.collect(Collectors.toList())
+				.toArray(new String[] {});
+	}
+
+	private Set<String> getFacilityCapabilities(Facility facility, String capabilitiesAttrName) {
+		Set<String> result = new HashSet<>();
+		PerunAttributeValue attrVal = getFacilityAttributeValue(facility, capabilitiesAttrName);
+		if (attrVal != null && !attrVal.isNullValue() && attrVal.valueAsList() != null) {
+			result = new HashSet<>(attrVal.valueAsList());
+		}
+
+		return result;
+	}
+
+	private Set<String> getCapabilities(Facility facility, String facilityCapabilitiesAttrName,
+										String resourceCapabilitiesAttrName, FilterBuilder[] parts) {
+		String facilityDN = PERUN_FACILITY_ID + '=' + facility.getId() + ',' + connectorLdap.getBaseDN();
+		FilterBuilder filter = and(equal(OBJECT_CLASS, PERUN_RESOURCE), equal(PERUN_FACILITY_DN, facilityDN), or(parts));
+		AttributeMapping capabilitiesMapping = getResourceAttributesMappingService().getMappingByIdentifier(resourceCapabilitiesAttrName);
+		String[] attributes = new String[] { capabilitiesMapping.getLdapName(), ASSIGNED_GROUP_ID };
+		EntryMapper<Set<String>> mapper = e -> {
+			Set<String> capabilities = new HashSet<>();
+			if (!checkHasAttributes(e, attributes)) {
+				return new HashSet<>();
+			}
+
+			Attribute capabilitiesAttr = e.get(capabilitiesMapping.getLdapName());
+			if (capabilitiesAttr != null) {
+				capabilitiesAttr.iterator().forEachRemaining(v -> capabilities.add(v.getString()));
+			}
+
+			return capabilities;
+		};
+		List<Set<String>> resourceCaps = connectorLdap.search(null, filter, SearchScope.SUBTREE, attributes, mapper);
+		Set<String> capabilities = new HashSet<>();
+		boolean includeFacilityCapabilities = false;
+		if (resourceCaps != null && !resourceCaps.isEmpty()) {
+			// if the mapper returns at least one entry, user is a member of some group assigned to the facility
+			includeFacilityCapabilities = true;
+			resourceCaps.stream()
+					.filter(Objects::nonNull)
+					.forEach(capabilities::addAll);
+		}
+
+		if (facilityCapabilitiesAttrName != null && includeFacilityCapabilities ) {
+			Set<String> facilityCapabilities = this.getFacilityCapabilities(facility, facilityCapabilitiesAttrName);
+			capabilities.addAll(facilityCapabilities);
+		}
+
+		return capabilities;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterLdapConstants.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterLdapConstants.java
new file mode 100644
index 000000000..9ada261cf
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterLdapConstants.java
@@ -0,0 +1,43 @@
+package cz.muni.ics.oidc.server.adapters.impl;
+
+public class PerunAdapterLdapConstants {
+
+    // COMMON
+    public static final String O = "o";
+    public static final String CN = "cn";
+    public static final String SN = "sn";
+    public static final String DESCRIPTION = "description";
+    public static final String OBJECT_CLASS = "objectClass";
+    public static final String OU_PEOPLE = "ou=People";
+    public static final String UUID = "uuid";
+
+    // USER
+    public static final String PERUN_USER = "perunUser";
+    public static final String PERUN_USER_ID = "perunUserId";
+    public static final String GIVEN_NAME = "givenName";
+    public static final String MEMBER_OF = "memberOf";
+    public static final String EDU_PERSON_PRINCIPAL_NAMES = "eduPersonPrincipalNames";
+
+    // GROUP
+    public static final String PERUN_GROUP = "perunGroup";
+    public static final String PERUN_GROUP_ID = "perunGroupId";
+    public static final String PERUN_PARENT_GROUP_ID = "perunParentGroupId";
+    public static final String PERUN_UNIQUE_GROUP_NAME = "perunUniqueGroupName";
+    public static final String UNIQUE_MEMBER = "uniqueMember";
+
+    // VO
+    public static final String PERUN_VO = "perunVO";
+    public static final String PERUN_VO_ID = "perunVoId";
+    public static final String MEMBER_OF_PERUN_VO = "memberOfPerunVo";
+
+    // RESOURCE
+    public static final String PERUN_RESOURCE = "perunResource";
+    public static final String PERUN_RESOURCE_ID = "perunResourceId";
+
+    // FACILITY
+    public static final String PERUN_FACILITY = "perunFacility";
+    public static final String PERUN_FACILITY_DN = "perunFacilityDn";
+    public static final String PERUN_FACILITY_ID = "perunFacilityId";
+    public static final String ASSIGNED_GROUP_ID = "assignedGroupId";
+    
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterRpc.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterRpc.java
new file mode 100644
index 000000000..0bd7c51f7
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterRpc.java
@@ -0,0 +1,1312 @@
+package cz.muni.ics.oidc.server.adapters.impl;
+
+import static cz.muni.ics.oidc.models.PerunAttributeValue.STRING_TYPE;
+import static cz.muni.ics.oidc.models.enums.MemberStatus.VALID;
+import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.ATTRIBUTES_MANAGER;
+import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.FACILITIES_MANAGER;
+import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.GROUPS_MANAGER;
+import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.MEMBERS_MANAGER;
+import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.REGISTRAR_MANAGER;
+import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.RESOURCES_MANAGER;
+import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.USERS_MANAGER;
+import static cz.muni.ics.oidc.server.connectors.PerunConnectorRpc.VOS_MANAGER;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+import cz.muni.ics.oidc.models.AttributeMapping;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.models.Member;
+import cz.muni.ics.oidc.models.Model;
+import cz.muni.ics.oidc.models.PerunAttribute;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.models.Resource;
+import cz.muni.ics.oidc.models.UserExtSource;
+import cz.muni.ics.oidc.models.Vo;
+import cz.muni.ics.oidc.models.enums.MemberStatus;
+import cz.muni.ics.oidc.models.enums.PerunEntityType;
+import cz.muni.ics.oidc.models.mappers.RpcMapper;
+import cz.muni.ics.oidc.server.PerunPrincipal;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.adapters.PerunAdapterMethods;
+import cz.muni.ics.oidc.server.adapters.PerunAdapterMethodsRpc;
+import cz.muni.ics.oidc.server.connectors.Affiliation;
+import cz.muni.ics.oidc.server.connectors.PerunConnectorRpc;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * Interface for fetching data from Perun via RPC
+ *
+ * @author Martin Kuba makub@ics.muni.cz
+ * @author Dominik František Bučík bucik@ics.muni.cz
+ * @author Peter Jancus jancus@ics.muni.cz
+ */
+public class PerunAdapterRpc extends PerunAdapterWithMappingServices implements PerunAdapterMethods, PerunAdapterMethodsRpc {
+
+	private final static Logger log = LoggerFactory.getLogger(PerunAdapterRpc.class);
+
+	private PerunConnectorRpc connectorRpc;
+
+	private String oidcClientIdAttr;
+	private String oidcCheckMembershipAttr;
+	private String orgUrlAttr;
+	private String affiliationsAttr;
+
+	public void setConnectorRpc(PerunConnectorRpc connectorRpc) {
+		this.connectorRpc = connectorRpc;
+	}
+
+	public void setOidcClientIdAttr(String oidcClientIdAttr) {
+		this.oidcClientIdAttr = oidcClientIdAttr;
+	}
+
+	public void setOidcCheckMembershipAttr(String oidcCheckMembershipAttr) {
+		this.oidcCheckMembershipAttr = oidcCheckMembershipAttr;
+	}
+
+	public void setOrgUrlAttr(String orgUrlAttr) {
+		this.orgUrlAttr = orgUrlAttr;
+	}
+
+	public void setAffiliationsAttr(String affiliationsAttr) {
+		this.affiliationsAttr = affiliationsAttr;
+	}
+
+	@Override
+	public PerunUser getPreauthenticatedUserId(PerunPrincipal perunPrincipal) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("extLogin", perunPrincipal.getExtLogin());
+		map.put("extSourceName", perunPrincipal.getExtSourceName());
+
+		JsonNode response = connectorRpc.post(USERS_MANAGER, "getUserByExtSourceNameAndExtLogin", map);
+		return RpcMapper.mapPerunUser(response);
+	}
+
+	@Override
+	public Facility getFacilityByClientId(String clientId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		} else if (!StringUtils.hasText(clientId)) {
+			return null;
+		}
+
+		AttributeMapping mapping = this.getFacilityAttributesMappingService().getMappingByIdentifier(oidcClientIdAttr);
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("attributeName", mapping.getRpcName());
+		map.put("attributeValue", clientId);
+		JsonNode jsonNode = connectorRpc.post(FACILITIES_MANAGER, "getFacilitiesByAttribute", map);
+
+		return (jsonNode.size() > 0) ? RpcMapper.mapFacility(jsonNode.get(0)) : null;
+	}
+
+	@Override
+	public boolean isMembershipCheckEnabledOnFacility(Facility facility) {
+		if (!this.connectorRpc.isEnabled()) {
+			return false;
+		}
+
+		AttributeMapping mapping = this.getFacilityAttributesMappingService().getMappingByIdentifier(oidcCheckMembershipAttr);
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("facility", facility.getId());
+		map.put("attributeName", mapping.getRpcName());
+		JsonNode res = connectorRpc.post(ATTRIBUTES_MANAGER, "getAttribute", map);
+
+		return res.get("value").asBoolean(false);
+	}
+
+	@Override
+	public boolean canUserAccessBasedOnMembership(Facility facility, Long userId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return true;
+		}
+
+		List<Group> activeGroups = getGroupsWhereUserIsActive(facility, userId);
+		return !activeGroups.isEmpty();
+	}
+
+	@Override
+	public Map<Vo, List<Group>> getGroupsForRegistration(Facility facility, Long userId, List<String> voShortNames) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		List<Vo> vos = getVosByShortNames(voShortNames);
+		Map<Long, Vo> vosMap = convertVoListToMap(vos);
+		List<Member> userMembers = getMembersByUser(userId);
+		userMembers = new ArrayList<>(new HashSet<>(userMembers));
+
+		//Filter out vos where member is other than valid or expired. These vos cannot be used for registration
+		Map<Long, MemberStatus> memberVoStatuses = convertMembersListToStatusesMap(userMembers);
+		Map<Long, Vo> vosForRegistration = new HashMap<>();
+		for (Map.Entry<Long, Vo> entry : vosMap.entrySet()) {
+			if (memberVoStatuses.containsKey(entry.getKey())) {
+				MemberStatus status = memberVoStatuses.get(entry.getKey());
+				if (VALID.equals(status) || MemberStatus.EXPIRED.equals(status)) {
+					vosForRegistration.put(entry.getKey(), entry.getValue());
+				}
+			} else {
+				vosForRegistration.put(entry.getKey(), entry.getValue());
+			}
+		}
+
+		// filter groups only if their VO is in the allowed VOs and if they have registration form
+		List<Group> allowedGroups = getAllowedGroups(facility);
+		List<Group> groupsForRegistration = allowedGroups.stream()
+				.filter(group -> vosForRegistration.containsKey(group.getVoId()) && hasApplicationForm(group))
+				.collect(Collectors.toList());
+
+		// create map for processing
+		Map<Vo, List<Group>> result = new HashMap<>();
+		for (Group group : groupsForRegistration) {
+			Vo vo = vosMap.get(group.getVoId());
+			if (!result.containsKey(vo)) {
+				result.put(vo, new ArrayList<>());
+			}
+			List<Group> list = result.get(vo);
+			list.add(group);
+		}
+
+		return result;
+	}
+
+	@Override
+	public boolean groupWhereCanRegisterExists(Facility facility) {
+		if (!this.connectorRpc.isEnabled()) {
+			return false;
+		}
+
+		List<Group> allowedGroups = getAllowedGroups(facility);
+
+		if (!allowedGroups.isEmpty()) {
+			for (Group group : allowedGroups) {
+				if (hasApplicationForm(group)) {
+					return true;
+				}
+			}
+		}
+
+		return false;
+	}
+
+	@Override
+	public boolean isUserInGroup(Long userId, Long groupId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return false;
+		}
+
+		Map<String, Object> groupParams = new LinkedHashMap<>();
+		groupParams.put("id", groupId);
+		JsonNode groupResponse = connectorRpc.post(GROUPS_MANAGER, "getGroupById", groupParams);
+		Group group = RpcMapper.mapGroup(groupResponse);
+
+		Map<String, Object> memberParams = new LinkedHashMap<>();
+		memberParams.put("vo", group.getVoId());
+		memberParams.put("user", userId);
+		JsonNode memberResponse = connectorRpc.post(MEMBERS_MANAGER, "getMemberByUser", memberParams);
+		Member member = RpcMapper.mapMember(memberResponse);
+
+		Map<String, Object> isGroupMemberParams = new LinkedHashMap<>();
+		isGroupMemberParams.put("group", groupId);
+		isGroupMemberParams.put("member", member.getId());
+		JsonNode res = connectorRpc.post(GROUPS_MANAGER, "isGroupMember", isGroupMemberParams);
+
+		return res.asBoolean(false);
+	}
+
+	@Override
+	public boolean setUserAttribute(Long userId, PerunAttribute attribute) {
+		if (!this.connectorRpc.isEnabled()) {
+			return true;
+		}
+
+		JsonNode attributeJson = attribute.toJson();
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("user", userId);
+		map.put("attribute", attributeJson);
+
+		JsonNode response = connectorRpc.post(ATTRIBUTES_MANAGER, "setAttribute", map);
+		return (response == null || response.isNull() || response instanceof NullNode);
+	}
+
+	@Override
+	public List<Affiliation> getUserExtSourcesAffiliations(Long userId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		List<UserExtSource> userExtSources = getUserExtSources(userId);
+		List<Affiliation> affiliations = new ArrayList<>();
+
+		AttributeMapping affMapping = new AttributeMapping("affMapping", affiliationsAttr, "", PerunAttributeValue.ARRAY_TYPE);
+		AttributeMapping orgUrlMapping = new AttributeMapping("orgUrl", orgUrlAttr, "", STRING_TYPE);
+		Set<AttributeMapping> attributeMappings = new HashSet<>(Arrays.asList(affMapping, orgUrlMapping));
+
+		for (UserExtSource ues : userExtSources) {
+			if ("cz.metacentrum.perun.core.impl.ExtSourceIdp".equals(ues.getExtSource().getType())) {
+				Map<String, PerunAttributeValue> uesAttrValues = getUserExtSourceAttributeValues(ues.getId(), attributeMappings);
+
+				long asserted = ues.getLastAccess().getTime() / 1000L;
+
+				String orgUrl = uesAttrValues.get(orgUrlMapping.getIdentifier()).valueAsString();
+				String affs = uesAttrValues.get(affMapping.getIdentifier()).valueAsString();
+				if (affs != null) {
+					for (String aff : affs.split(";")) {
+						String source = ( (orgUrl != null) ? orgUrl : ues.getExtSource().getName() );
+						Affiliation affiliation = new Affiliation(source, aff, asserted);
+						log.debug("found {} from IdP {} with orgURL {} asserted at {}", aff, ues.getExtSource().getName(),
+								orgUrl, asserted);
+						affiliations.add(affiliation);
+					}
+				}
+			}
+		}
+
+		return affiliations;
+	}
+
+	@Override
+	public List<Affiliation> getGroupAffiliations(Long userId, String groupAffiliationsAttr) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		List<Affiliation> affiliations = new ArrayList<>();
+
+		List<Member> userMembers = getMembersByUser(userId);
+		for (Member member : userMembers) {
+			if (VALID.equals(member.getStatus())) {
+				List<Group> memberGroups = getMemberGroups(member.getId());
+				for (Group group : memberGroups) {
+					PerunAttributeValue attrValue = this.getGroupAttributeValue(group, groupAffiliationsAttr);
+					if (attrValue != null && attrValue.valueAsString() != null) {
+						long linuxTime = System.currentTimeMillis() / 1000L;
+						for (String value : attrValue.valueAsList()) {
+							Affiliation affiliation = new Affiliation(null, value, linuxTime);
+							log.debug("found {} on group {}", value, group.getName());
+							affiliations.add(affiliation);
+						}
+					}
+				}
+			}
+		}
+
+		return affiliations;
+	}
+
+	@Override
+	public List<String> getGroupsAssignedToResourcesWithUniqueNames(Facility facility) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		List<Resource> resources = getAssignedResources(facility);
+		List<String> result = new ArrayList<>();
+
+		String voShortName = "urn:perun:group:attribute-def:virt:voShortName";
+
+		for (Resource res : resources) {
+			List<Group> groups = getRichGroupsAssignedToResourceWithAttributesByNames(res, Collections.singletonList(voShortName));
+
+			for (Group group : groups) {
+				if (group.getAttributeByUrnName(voShortName) != null &&
+						group.getAttributeByUrnName(voShortName).hasNonNull("value")) {
+					String value = group.getAttributeByUrnName(voShortName).get("value").textValue();
+					group.setUniqueGroupName(value + ":" + group.getName());
+					result.add(group.getUniqueGroupName());
+				}
+			}
+		}
+
+		return result;
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getEntitylessAttributes(String attributeName) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		Map<String, Object> attrNameMap = new LinkedHashMap<>();
+		attrNameMap.put("attrName", attributeName);
+		JsonNode entitylessAttributesJson = connectorRpc.post(ATTRIBUTES_MANAGER, "getEntitylessAttributes", attrNameMap);
+
+		Long attributeDefinitionId = RpcMapper.mapAttribute(entitylessAttributesJson.get(0)).getId();
+
+		Map<String, Object> attributeDefinitionIdMap = new LinkedHashMap<>();
+		attributeDefinitionIdMap.put("attributeDefinition", attributeDefinitionId);
+		JsonNode entitylessKeysJson = connectorRpc.post(ATTRIBUTES_MANAGER, "getEntitylessKeys", attributeDefinitionIdMap);
+
+		Map<String, PerunAttribute> result = new LinkedHashMap<>();
+
+		for(int i = 0; i < entitylessKeysJson.size(); i++) {
+			result.put(entitylessKeysJson.get(i).asText(), RpcMapper.mapAttribute(entitylessAttributesJson.get(i)));
+		}
+
+		if (result.size() == 0) {
+			return null;
+		}
+
+		return result;
+	}
+
+	@Override
+	public Vo getVoByShortName(String shortName) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		Map<String, Object> params = new LinkedHashMap<>();
+		params.put("shortName", shortName);
+
+		JsonNode jsonNode = connectorRpc.post(VOS_MANAGER, "getVoByShortName", params);
+		return RpcMapper.mapVo(jsonNode);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getUserAttributeValues(PerunUser user, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getUserAttributeValues(user.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getUserAttributeValues(Long userId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		Map<String, PerunAttribute> userAttributes = this.getUserAttributes(userId, attrsToFetch);
+		return extractValues(userAttributes);
+	}
+
+	@Override
+	public PerunAttributeValue getUserAttributeValue(PerunUser user, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getUserAttributeValue(user.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getUserAttributeValue(Long userId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getUserAttribute(userId, attrToFetch).toPerunAttributeValue();
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getFacilityAttributeValues(Facility facility, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getFacilityAttributeValues(facility.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getFacilityAttributeValues(Long facilityId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		Map<String, PerunAttribute> facilityAttributes = this.getFacilityAttributes(facilityId, attrsToFetch);
+		return extractValues(facilityAttributes);
+	}
+
+	@Override
+	public PerunAttributeValue getFacilityAttributeValue(Facility facility, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getFacilityAttributeValue(facility.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getFacilityAttributeValue(Long facilityId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getFacilityAttribute(facilityId, attrToFetch).toPerunAttributeValue();
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getVoAttributeValues(Vo vo, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getVoAttributeValues(vo.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getVoAttributeValues(Long voId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		Map<String, PerunAttribute> voAttributes = this.getVoAttributes(voId, attrsToFetch);
+		return extractValues(voAttributes);
+	}
+
+	@Override
+	public PerunAttributeValue getVoAttributeValue(Vo vo, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getVoAttributeValue(vo.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getVoAttributeValue(Long voId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getVoAttribute(voId, attrToFetch).toPerunAttributeValue();
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getGroupAttributeValues(Group group, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getGroupAttributeValues(group.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getGroupAttributeValues(Long groupId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		Map<String, PerunAttribute> groupAttributes = this.getGroupAttributes(groupId, attrsToFetch);
+		return extractValues(groupAttributes);
+	}
+
+	@Override
+	public PerunAttributeValue getGroupAttributeValue(Group group, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getGroupAttributeValue(group.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getGroupAttributeValue(Long groupId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getGroupAttribute(groupId, attrToFetch).toPerunAttributeValue();
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getResourceAttributeValues(Resource resource, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getResourceAttributeValues(resource.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttributeValue> getResourceAttributeValues(Long resourceId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		Map<String, PerunAttribute> resourceAttributes = this.getResourceAttributes(resourceId, attrsToFetch);
+		return extractValues(resourceAttributes);
+	}
+
+	@Override
+	public PerunAttributeValue getResourceAttributeValue(Resource resource, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getResourceAttributeValue(resource.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttributeValue getResourceAttributeValue(Long resourceId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getResourceAttribute(resourceId, attrToFetch).toPerunAttributeValue();
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getFacilityAttributes(Facility facility, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getFacilityAttributes(facility.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getFacilityAttributes(Long facilityId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return getAttributes(PerunEntityType.FACILITY, facilityId, attrsToFetch);
+	}
+
+	@Override
+	public PerunAttribute getFacilityAttribute(Facility facility, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getFacilityAttribute(facility.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttribute getFacilityAttribute(Long facilityId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return getAttribute(PerunEntityType.FACILITY, facilityId, attrToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getGroupAttributes(Group group, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getGroupAttributes(group.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getGroupAttributes(Long groupId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return getAttributes(PerunEntityType.GROUP, groupId, attrsToFetch);
+	}
+
+	@Override
+	public PerunAttribute getGroupAttribute(Group group, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getGroupAttribute(group.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttribute getGroupAttribute(Long groupId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return getAttribute(PerunEntityType.GROUP, groupId, attrToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getUserAttributes(PerunUser user, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getUserAttributes(user.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getUserAttributes(Long userId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return getAttributes(PerunEntityType.USER, userId, attrsToFetch);
+	}
+
+	@Override
+	public PerunAttribute getUserAttribute(PerunUser user, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getUserAttribute(user.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttribute getUserAttribute(Long userId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return getAttribute(PerunEntityType.USER, userId, attrToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getVoAttributes(Vo vo, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getVoAttributes(vo.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getVoAttributes(Long voId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return getAttributes(PerunEntityType.VO, voId, attrsToFetch);
+	}
+
+	@Override
+	public PerunAttribute getVoAttribute(Vo vo, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getVoAttribute(vo.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttribute getVoAttribute(Long voId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return getAttribute(PerunEntityType.VO, voId, attrToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getResourceAttributes(Resource resource, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return this.getResourceAttributes(resource.getId(), attrsToFetch);
+	}
+
+	@Override
+	public Map<String, PerunAttribute> getResourceAttributes(Long resourceId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		return getAttributes(PerunEntityType.RESOURCE, resourceId, attrsToFetch);
+	}
+
+	@Override
+	public PerunAttribute getResourceAttribute(Resource resource, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return this.getResourceAttribute(resource.getId(), attrToFetch);
+	}
+
+	@Override
+	public PerunAttribute getResourceAttribute(Long resourceId, String attrToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		return getAttribute(PerunEntityType.RESOURCE, resourceId, attrToFetch);
+	}
+
+	@Override
+	public Set<String> getCapabilities(Facility facility, Set<String> groupNames,
+									   String facilityCapabilitiesAttrName,
+									   String resourceCapabilitiesAttrName)
+	{
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashSet<>();
+		}
+
+		if (facility == null) {
+			return new HashSet<>();
+		}
+
+		Set<String> capabilities = new HashSet<>();
+		Set<String> resourceGroupNames = new HashSet<>();
+
+		if (null != resourceCapabilitiesAttrName) {
+			List<Resource> resources = this.getAssignedRichResources(facility);
+			for (Resource resource : resources) {
+				PerunAttributeValue attrValue = this.getResourceAttributeValue(resource.getId(), resourceCapabilitiesAttrName);
+
+				List<String> resourceCapabilities = attrValue.valueAsList();
+				if (resourceCapabilities == null || resourceCapabilities.size() == 0) {
+					continue;
+				}
+				List<Group> groups = this.getAssignedGroups(resource.getId());
+				for (Group group : groups) {
+					resourceGroupNames.add(group.getName());
+					String groupName = group.getName();
+					if (resource.getVo() != null) {
+						groupName = resource.getVo().getShortName() + ':' + groupName;
+					}
+					group.setUniqueGroupName(groupName);
+
+					if (groupNames.contains(groupName)) {
+						log.trace("Group [{}] found in users groups, add capabilities [{}]", groupName, resourceCapabilities);
+						capabilities.addAll(resourceCapabilities);
+					} else {
+						log.trace("Group [{}] not found in users groups, continue to the next one", groupName);
+					}
+				}
+			}
+		}
+
+		if (null != facilityCapabilitiesAttrName && !Collections.disjoint(groupNames, resourceGroupNames)) {
+			Set<String> facilityCapabilities = this.getFacilityCapabilities(facility, facilityCapabilitiesAttrName);
+			capabilities.addAll(facilityCapabilities);
+		}
+
+		return capabilities;
+	}
+
+	@Override
+	public Set<String> getCapabilities(Facility facility, Map<Long, String> idToGnameMap,
+									   String facilityCapabilitiesAttrName, String resourceCapabilitiesAttrName)
+	{
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashSet<>();
+		}
+
+		if (facility == null) {
+			return new HashSet<>();
+		}
+
+		return this.getCapabilities(facility, new HashSet<>(idToGnameMap.values()), facilityCapabilitiesAttrName,
+				resourceCapabilitiesAttrName);
+	}
+
+	@Override
+	public Set<Group> getGroupsWhereUserIsActiveWithUniqueNames(Long facilityId, Long userId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashSet<>();
+		}
+
+		Set<Group> groups = this.getGroupsWhereUserIsActive(facilityId, userId);
+
+		Map<Long, String> voIdToShortNameMap = new HashMap<>();
+		groups.forEach(g -> {
+			if (!voIdToShortNameMap.containsKey(g.getVoId())) {
+				Vo vo = this.getVoById(g.getVoId());
+				if (vo != null) {
+					voIdToShortNameMap.put(vo.getId(), vo.getShortName());
+				}
+			}
+			g.setUniqueGroupName(voIdToShortNameMap.get(g.getVoId()) + ':' + g.getName());
+		});
+
+		return groups;
+	}
+
+	@Override
+	public Set<Long> getUserGroupsIds(Long userId, Long voId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashSet<>();
+		}
+
+		Member member = getMemberByUser(userId, voId);
+		Set<Long> groups = new HashSet<>();
+		if (member != null) {
+			groups = getMemberGroups(member.getId()).stream().map(Group::getId).collect(Collectors.toSet());
+		}
+
+		return groups;
+	}
+
+	@Override
+	public boolean isValidMemberInGroupsAndVos(Long userId, Set<Long> mandatoryVos, Set<Long> mandatoryGroups,
+											   Set<Long> envVos, Set<Long> envGroups) {
+		List<Member> members = getMembersByUser(userId);
+		Set<Long> foundVoIds = new HashSet<>();
+		Set<Long> foundGroupIds = new HashSet<>();
+		boolean skipGroups = mandatoryGroups.isEmpty() && envGroups.isEmpty();
+		for (Member m: members) {
+			if (MemberStatus.VALID.equals(m.getStatus())) {
+				foundVoIds.add(m.getVoId());
+			}
+			if (!skipGroups) {
+				foundGroupIds.addAll(getMemberGroups(m.getId()).stream().map(Model::getId).collect(Collectors.toList()));
+			}
+		}
+
+		return PerunAdapter.decideAccess(foundVoIds, foundGroupIds, mandatoryVos, mandatoryGroups, envVos, envGroups);
+	}
+
+	@Override
+	public boolean isValidMemberInGroupsAndVos(Long userId, Set<Long> vos, Set<Long> groups) {
+		List<Member> members = getMembersByUser(userId);
+		Set<Long> foundVoIds = new HashSet<>();
+		Set<Long> foundGroupIds = new HashSet<>();
+		boolean skipGroups = groups.isEmpty();
+
+		for (Member m: members) {
+			if (MemberStatus.VALID.equals(m.getStatus())) {
+				foundVoIds.add(m.getVoId());
+			}
+			if (!skipGroups) {
+				foundGroupIds.addAll(getMemberGroups(m.getId()).stream().map(Model::getId).collect(Collectors.toList()));
+			}
+		}
+
+		return PerunAdapter.decideAccess(foundVoIds, foundGroupIds, vos, groups);
+	}
+
+	@Override
+	public boolean isUserInVo(Long userId, String voShortName) {
+		if (userId == null) {
+			throw new IllegalArgumentException("No userId");
+		} else if (!StringUtils.hasText(voShortName)) {
+			throw new IllegalArgumentException("No voShortName");
+		}
+
+		Vo vo = getVoByShortName(voShortName);
+		if (vo == null || vo.getId() == null) {
+			log.debug("isUserInVo - No VO found, returning false");
+			return false;
+		}
+		try {
+			Member member = getMemberByUser(userId, vo.getId());
+			if (member == null) {
+				log.debug("isUserInVo - No member found, returning false");
+				return false;
+			}
+			return VALID.equals(member.getStatus());
+		} catch (Exception e) {
+			log.debug("isUserInVo - caught exception, probably user is not a member");
+			log.trace("{}", e.getMessage(), e);
+			return false;
+		}
+	}
+
+	@Override
+	public boolean hasApplicationForm(String voShortName) {
+		if (!this.connectorRpc.isEnabled()) {
+			return false;
+		}
+
+		Vo vo = getVoByShortName(voShortName);
+		if (vo == null || vo.getId() == null) {
+			return false;
+		}
+		return hasApplicationForm(vo.getId());
+	}
+
+	private Member getMemberByUser(Long userId, Long voId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		Map<String, Object> params = new LinkedHashMap<>();
+		params.put("user", userId);
+		params.put("vo", voId);
+		JsonNode jsonNode = connectorRpc.post(MEMBERS_MANAGER, "getMemberByUser", params);
+
+		return RpcMapper.mapMember(jsonNode);
+	}
+
+	private Set<Group> getGroupsWhereUserIsActive(Long facilityId, Long userId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashSet<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("facility", facilityId);
+		map.put("user", userId);
+		JsonNode res = connectorRpc.post(USERS_MANAGER, "getGroupsWhereUserIsActive", map);
+
+		return new HashSet<>(RpcMapper.mapGroups(res));
+	}
+
+	private Vo getVoById(Long voId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("id", voId);
+
+		JsonNode res = connectorRpc.post(VOS_MANAGER, "getVoById", map);
+		return RpcMapper.mapVo(res);
+	}
+
+	private List<Group> getAssignedGroups(Long resourceId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> params = new LinkedHashMap<>();
+		params.put("resource", resourceId);
+
+		JsonNode response = connectorRpc.post(RESOURCES_MANAGER, "getAssignedGroups", params);
+
+		return RpcMapper.mapGroups(response);
+	}
+
+	private Map<String, PerunAttributeValue> extractValues(Map<String, PerunAttribute> attributeMap) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+		
+		Map<String, PerunAttributeValue> resultMap = new LinkedHashMap<>();
+		for (Map.Entry<String, PerunAttribute> attrPair: attributeMap.entrySet()) {
+			String attrName = attrPair.getKey();
+			PerunAttribute attr = attrPair.getValue();
+			if (attr != null) {
+				resultMap.put(attrName, attr.toPerunAttributeValue());
+			}
+		}
+
+		return resultMap;
+	}
+
+	private Map<String, PerunAttribute> getAttributes(PerunEntityType entity, Long entityId, Collection<String> attrsToFetch) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		} else if (attrsToFetch == null || attrsToFetch.isEmpty()) {
+			return new HashMap<>();
+		}
+
+		Set<AttributeMapping> mappings;
+		switch (entity) {
+			case USER: mappings = this.getUserAttributesMappingService()
+					.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			case FACILITY: mappings = this.getFacilityAttributesMappingService()
+					.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			case VO: mappings = this.getVoAttributesMappingService()
+					.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			case GROUP: mappings = this.getGroupAttributesMappingService()
+					.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			case RESOURCE: mappings = this.getResourceAttributesMappingService()
+					.getMappingsByIdentifiers(attrsToFetch);
+				break;
+			default: mappings  = new HashSet<>();
+				break;
+		}
+
+		List<String> rpcNames = mappings.stream().map(AttributeMapping::getRpcName).collect(Collectors.toList());
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put(entity.toString().toLowerCase(), entityId);
+		map.put("attrNames", rpcNames);
+
+		JsonNode res = connectorRpc.post(ATTRIBUTES_MANAGER, "getAttributes", map);
+		return RpcMapper.mapAttributes(res, mappings);
+	}
+
+	private List<Group> getMemberGroups(Long memberId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("member", memberId);
+
+		JsonNode response = connectorRpc.post(GROUPS_MANAGER, "getMemberGroups", map);
+		return RpcMapper.mapGroups(response);
+	}
+
+	private List<Member> getMembersByUser(Long userId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> params = new LinkedHashMap<>();
+		params.put("user", userId);
+		JsonNode jsonNode = connectorRpc.post(MEMBERS_MANAGER, "getMembersByUser", params);
+
+		return RpcMapper.mapMembers(jsonNode);
+	}
+
+	private List<Vo> getVosByShortNames(List<String> voShortNames) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		List<Vo> vos = new ArrayList<>();
+		for (String shortName : voShortNames) {
+			Vo vo = getVoByShortName(shortName);
+			vos.add(vo);
+		}
+
+		return vos;
+	}
+
+	private Map<Long, MemberStatus> convertMembersListToStatusesMap(List<Member> userMembers) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+		
+		Map<Long, MemberStatus> res = new HashMap<>();
+		for (Member m : userMembers) {
+			res.put(m.getVoId(), m.getStatus());
+		}
+
+		return res;
+	}
+
+	private Map<String, PerunAttributeValue> getUserExtSourceAttributeValues(Long uesId, Set<AttributeMapping> attrMappings) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("userExtSource", uesId);
+		map.put("attrNames", attrMappings.stream().map(AttributeMapping::getRpcName).collect(Collectors.toList()));
+
+		JsonNode response = connectorRpc.post(ATTRIBUTES_MANAGER, "getAttributes", map);
+		Map<String, PerunAttribute> attributeMap = RpcMapper.mapAttributes(response, attrMappings);
+		return extractValues(attributeMap);
+	}
+
+	private List<Group> getAllowedGroups(Facility facility) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("facility", facility.getId());
+		JsonNode jsonNode = connectorRpc.post(FACILITIES_MANAGER, "getAllowedGroups", map);
+		List<Group> result = new ArrayList<>();
+		for (int i = 0; i < jsonNode.size(); i++) {
+			JsonNode groupNode = jsonNode.get(i);
+			result.add(RpcMapper.mapGroup(groupNode));
+		}
+
+		return result;
+	}
+
+	private boolean hasApplicationForm(Group group) {
+		if (!this.connectorRpc.isEnabled()) {
+			return false;
+		}
+		
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("group", group.getId());
+		try {
+			if (group.getName().equalsIgnoreCase("members")) {
+				log.debug("hasApplicationForm({}) continues to call regForm for VO {}", group, group.getVoId());
+				return hasApplicationForm(group.getVoId());
+			} else {
+				connectorRpc.post(REGISTRAR_MANAGER, "getApplicationForm", map);
+			}
+		} catch (Exception e) {
+			// when group does not have form exception is thrown. Every error thus is supposed as group without form
+			// this method will be used after calling other RPC methods - if RPC is not available other methods should discover it first
+			return false;
+		}
+
+		return true;
+	}
+
+	private boolean hasApplicationForm(Long voId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return false;
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("vo", voId);
+		try {
+			connectorRpc.post(REGISTRAR_MANAGER, "getApplicationForm", map);
+		} catch (Exception e) {
+			// when vo does not have form exception is thrown. Every error thus is supposed as vo without form
+			// this method will be used after calling other RPC methods - if RPC is not available other methods should discover it first
+			return false;
+		}
+
+		return true;
+	}
+
+	private List<Group> getGroupsWhereUserIsActive(Facility facility, Long userId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("facility", facility.getId());
+		map.put("user", userId);
+		JsonNode jsonNode = connectorRpc.post(USERS_MANAGER, "getGroupsWhereUserIsActive", map);
+
+		return RpcMapper.mapGroups(jsonNode);
+	}
+
+	private Map<Long, Vo> convertVoListToMap(List<Vo> vos) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashMap<>();
+		}
+		
+		Map<Long, Vo> map = new HashMap<>();
+		for (Vo vo : vos) {
+			map.put(vo.getId(), vo);
+		}
+
+		return map;
+	}
+
+	private List<Resource> getAssignedResources(Facility facility) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("facility", facility.getId());
+
+		JsonNode res = connectorRpc.post(FACILITIES_MANAGER, "getAssignedResources", map);
+		return RpcMapper.mapResources(res);
+	}
+
+	private List<Resource> getAssignedRichResources(Facility facility) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("facility", facility.getId());
+
+		JsonNode res = connectorRpc.post(FACILITIES_MANAGER, "getAssignedRichResources", map);
+		return RpcMapper.mapResources(res);
+	}
+
+	private List<Group> getRichGroupsAssignedToResourceWithAttributesByNames(Resource resource, List<String> attrNames) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		Set<AttributeMapping> mappings = this.getGroupAttributesMappingService()
+				.getMappingsByIdentifiers(attrNames);
+		List<String> rpcNames = mappings.stream().map(AttributeMapping::getRpcName).collect(Collectors.toList());
+		map.put("resource", resource.getId());
+		map.put("attrNames", rpcNames);
+
+		JsonNode res = connectorRpc.post(GROUPS_MANAGER, "getRichGroupsAssignedToResourceWithAttributesByNames", map);
+		List<Group> groups = new ArrayList<>();
+
+		for (int i = 0; i < res.size(); i++) {
+			JsonNode jsonNode = res.get(i);
+			Group group = RpcMapper.mapGroup(jsonNode);
+
+			JsonNode groupAttrs = jsonNode.get("attributes");
+			Map<String, JsonNode> attrsMap = new HashMap<>();
+
+			for (int j = 0; j < groupAttrs.size(); j++) {
+				JsonNode attr = groupAttrs.get(j);
+
+				String namespace = attr.get("namespace").textValue();
+				String friendlyName = attr.get("friendlyName").textValue();
+
+				attrsMap.put(namespace + ":" + friendlyName, attr);
+			}
+
+			group.setAttributes(attrsMap);
+			groups.add(group);
+		}
+
+		return groups;
+	}
+
+	private PerunAttribute getAttribute(PerunEntityType entity, Long entityId, String attributeName) {
+		if (!this.connectorRpc.isEnabled()) {
+			return null;
+		}
+
+		AttributeMapping mapping;
+		switch (entity) {
+			case USER: mapping = this.getUserAttributesMappingService()
+					.getMappingByIdentifier(attributeName);
+				break;
+			case FACILITY: mapping = this.getFacilityAttributesMappingService()
+					.getMappingByIdentifier(attributeName);
+				break;
+			case VO: mapping = this.getVoAttributesMappingService()
+					.getMappingByIdentifier(attributeName);
+				break;
+			case GROUP: mapping = this.getGroupAttributesMappingService()
+					.getMappingByIdentifier(attributeName);
+				break;
+			case RESOURCE: mapping = this.getResourceAttributesMappingService()
+					.getMappingByIdentifier(attributeName);
+				break;
+			default:
+				throw new IllegalArgumentException("Unrecognized entity");
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put(entity.toString().toLowerCase(), entityId);
+		map.put("attributeName", mapping.getRpcName());
+
+		JsonNode res = connectorRpc.post(ATTRIBUTES_MANAGER, "getAttribute", map);
+		return RpcMapper.mapAttribute(res);
+	}
+
+	private List<UserExtSource> getUserExtSources(Long userId) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new ArrayList<>();
+		}
+
+		Map<String, Object> map = new LinkedHashMap<>();
+		map.put("user", userId);
+
+		JsonNode response = connectorRpc.post(USERS_MANAGER, "getUserExtSources", map);
+		return RpcMapper.mapUserExtSources(response);
+	}
+
+	private Set<String> getFacilityCapabilities(Facility facility, String capabilitiesAttrName) {
+		if (!this.connectorRpc.isEnabled()) {
+			return new HashSet<>();
+		}
+
+		Set<String> capabilities = new HashSet<>();
+		if (facility != null) {
+			PerunAttributeValue attr = getFacilityAttributeValue(facility, capabilitiesAttrName);
+			if (attr != null && attr.valueAsList() != null) {
+				capabilities = new HashSet<>(attr.valueAsList());
+			}
+		}
+
+		return capabilities;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterWithMappingServices.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterWithMappingServices.java
new file mode 100644
index 000000000..f6ba47d86
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/adapters/impl/PerunAdapterWithMappingServices.java
@@ -0,0 +1,75 @@
+package cz.muni.ics.oidc.server.adapters.impl;
+
+import cz.muni.ics.oidc.server.AttributeMappingsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+/**
+ * Abstract class containing different mapping services. Adapter implementation should extend this class if
+ * the implementation needs to use the mapping service.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public abstract class PerunAdapterWithMappingServices {
+
+	@Autowired
+	@Qualifier("userAttributesMappingService")
+	private AttributeMappingsService userAttributesMappingService;
+
+	@Autowired
+	@Qualifier("facilityAttributesMappingService")
+	private AttributeMappingsService facilityAttributesMappingService;
+
+	@Autowired
+	@Qualifier("groupAttributesMappingService")
+	private AttributeMappingsService groupAttributesMappingService;
+
+	@Autowired
+	@Qualifier("voAttributesMappingService")
+	private AttributeMappingsService voAttributesMappingService;
+
+	@Autowired
+	@Qualifier("resourceAttributesMappingService")
+	private AttributeMappingsService resourceAttributesMappingService;
+
+	public AttributeMappingsService getUserAttributesMappingService() {
+		return userAttributesMappingService;
+	}
+
+	public void setUserAttributesMappingService(AttributeMappingsService userAttributesMappingService) {
+		this.userAttributesMappingService = userAttributesMappingService;
+	}
+
+	public AttributeMappingsService getFacilityAttributesMappingService() {
+		return facilityAttributesMappingService;
+	}
+
+	public void setFacilityAttributesMappingService(AttributeMappingsService facilityAttributesMappingService) {
+		this.facilityAttributesMappingService = facilityAttributesMappingService;
+	}
+
+	public AttributeMappingsService getGroupAttributesMappingService() {
+		return groupAttributesMappingService;
+	}
+
+	public void setGroupAttributesMappingService(AttributeMappingsService groupAttributesMappingService) {
+		this.groupAttributesMappingService = groupAttributesMappingService;
+	}
+
+	public AttributeMappingsService getVoAttributesMappingService() {
+		return voAttributesMappingService;
+	}
+
+	public void setVoAttributesMappingService(AttributeMappingsService voAttributesMappingService) {
+		this.voAttributesMappingService = voAttributesMappingService;
+	}
+
+	public AttributeMappingsService getResourceAttributesMappingService() {
+		return resourceAttributesMappingService;
+	}
+
+	public void setResourceAttributesMappingService(AttributeMappingsService resourceAttributesMappingService) {
+		this.resourceAttributesMappingService = resourceAttributesMappingService;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimContextCommonParameters.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimContextCommonParameters.java
new file mode 100644
index 000000000..92d1fbe06
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimContextCommonParameters.java
@@ -0,0 +1,21 @@
+package cz.muni.ics.oidc.server.claims;
+
+import cz.muni.ics.oidc.models.Facility;
+
+public class ClaimContextCommonParameters {
+
+    private Facility client;
+
+    public ClaimContextCommonParameters(Facility client) {
+        this.client = client;
+    }
+
+    public Facility getClient() {
+        return client;
+    }
+
+    public void setClient(Facility client) {
+        this.client = client;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimModifier.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimModifier.java
new file mode 100644
index 000000000..0c51e3b6e
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimModifier.java
@@ -0,0 +1,47 @@
+package cz.muni.ics.oidc.server.claims;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Interface for all code that needs to modify claim values.
+ *
+ * @see cz.muni.ics.oidc.server.claims.modifiers for different implementations of claim value modifiers
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public abstract class ClaimModifier {
+
+	private static final Logger log = LoggerFactory.getLogger(ClaimModifier.class);
+
+	private final String claimName;
+	private final String modifierName;
+
+	public ClaimModifier(ClaimModifierInitContext ctx) {
+		this.claimName = ctx.getClaimName();
+		this.modifierName = ctx.getModifierName();
+		log.debug("{} - claim modifier initialized", ctx.getClaimName());
+	}
+
+	public String getClaimName() {
+		return claimName;
+	}
+
+	public String getModifierName() {
+		return modifierName;
+	}
+
+	public String getUnifiedName() {
+		return claimName + ':' + modifierName;
+	}
+
+	public abstract String modify(String value);
+
+	@Override
+	public String toString() {
+		return this.getClass().getSimpleName() + '{' +
+				"claimName='" + claimName + '\'' +
+				", modifierName='" + modifierName + '\'' +
+				'}';
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimModifierInitContext.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimModifierInitContext.java
new file mode 100644
index 000000000..aa7e9d71d
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimModifierInitContext.java
@@ -0,0 +1,42 @@
+package cz.muni.ics.oidc.server.claims;
+
+import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Context for initializing ClaimModifiers.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class ClaimModifierInitContext {
+
+	private static final Logger log = LoggerFactory.getLogger(ClaimModifierInitContext.class);
+
+	private final String propertyPrefix;
+	private final Properties properties;
+	private final String claimName;
+	private final String modifierName;
+
+	public ClaimModifierInitContext(String propertyPrefix, Properties properties, String claimName, String modifierName) {
+		this.propertyPrefix = propertyPrefix;
+		this.properties = properties;
+		this.claimName = claimName;
+		this.modifierName = modifierName;
+		log.debug("{}:{} - context: property prefix for modifier configured to '{}'",
+				claimName, modifierName, propertyPrefix);
+	}
+
+	public String getClaimName() {
+		return claimName;
+	}
+
+	public String getModifierName() {
+		return modifierName;
+	}
+
+	public String getProperty(String suffix, String defaultValue) {
+		return properties.getProperty(propertyPrefix + '.' + suffix, defaultValue);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSource.java
new file mode 100644
index 000000000..a80b0dcb4
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSource.java
@@ -0,0 +1,39 @@
+package cz.muni.ics.oidc.server.claims;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Interface for code that can produce claim values.
+ *
+ * @see cz.muni.ics.oidc.server.claims.sources for different implementations of claim sources
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public abstract class ClaimSource {
+
+	private static final Logger log = LoggerFactory.getLogger(ClaimSource.class);
+
+	private final String claimName;
+
+	public ClaimSource(ClaimSourceInitContext ctx) {
+		this.claimName = ctx.getClaimName();
+		log.debug("{} - claim source initialized", getClaimName());
+	}
+
+	public String getClaimName() {
+		return claimName;
+	}
+
+	public abstract Set<String> getAttrIdentifiers();
+
+	public abstract JsonNode produceValue(ClaimSourceProduceContext pctx);
+
+	@Override
+	public String toString() {
+		return this.getClass().getName();
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSourceInitContext.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSourceInitContext.java
new file mode 100644
index 000000000..c3af8de71
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSourceInitContext.java
@@ -0,0 +1,54 @@
+package cz.muni.ics.oidc.server.claims;
+
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import java.util.Properties;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Context for initializing ClaimValueSources.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class ClaimSourceInitContext {
+
+	private static final Logger log = LoggerFactory.getLogger(ClaimSourceInitContext.class);
+
+	private final PerunOidcConfig perunOidcConfig;
+	private final JWTSigningAndValidationService jwtService;
+	private final String propertyPrefix;
+	private final Properties properties;
+	private final String claimName;
+
+	public ClaimSourceInitContext(PerunOidcConfig perunOidcConfig,
+								  JWTSigningAndValidationService jwtService,
+								  String propertyPrefix,
+								  Properties properties,
+								  String claimName)
+	{
+		this.perunOidcConfig = perunOidcConfig;
+		this.jwtService = jwtService;
+		this.propertyPrefix = propertyPrefix;
+		this.properties = properties;
+		this.claimName = claimName;
+		log.debug("{} - context: property prefix for modifier configured to '{}'", claimName, propertyPrefix);
+	}
+
+	public String getClaimName() {
+		return claimName;
+	}
+
+	public String getProperty(String suffix, String defaultValue) {
+		return properties.getProperty(propertyPrefix + "." + suffix, defaultValue);
+	}
+
+	public JWTSigningAndValidationService getJwtService() {
+		return jwtService;
+	}
+
+	public PerunOidcConfig getPerunOidcConfig() {
+		return perunOidcConfig;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSourceProduceContext.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSourceProduceContext.java
new file mode 100644
index 000000000..f132594a2
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimSourceProduceContext.java
@@ -0,0 +1,68 @@
+package cz.muni.ics.oidc.server.claims;
+
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import java.util.Map;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+
+/**
+ * Context in which the value of the claim is produced.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class ClaimSourceProduceContext {
+
+	private final long perunUserId;
+	private final String sub;
+	private final Map<String, PerunAttributeValue> attrValues;
+	private final PerunAdapter perunAdapter;
+	private final ClientDetailsEntity client;
+	private final ClaimContextCommonParameters contextCommonParameters;
+
+	public ClaimSourceProduceContext(long perunUserId,
+									 String sub,
+									 Map<String, PerunAttributeValue> attrValues,
+									 PerunAdapter perunAdapter,
+									 ClientDetailsEntity client,
+									 ClaimContextCommonParameters contextCommonParameters)
+	{
+		this.perunUserId = perunUserId;
+		this.sub = sub;
+		this.attrValues = attrValues;
+		this.perunAdapter = perunAdapter;
+		this.client = client;
+		this.contextCommonParameters = contextCommonParameters;
+	}
+
+	public Map<String, PerunAttributeValue> getAttrValues() {
+		return attrValues;
+	}
+
+	public long getPerunUserId() {
+		return perunUserId;
+	}
+
+	public String getSub() {
+		return sub;
+	}
+
+	public PerunAdapter getPerunAdapter() {
+		return perunAdapter;
+	}
+
+	public ClientDetailsEntity getClient() {
+		return client;
+	}
+
+	public ClaimContextCommonParameters getContextCommonParameters() {
+		return contextCommonParameters;
+	}
+
+	@Override
+	public String toString() {
+		return "ClaimSourceProduceContext{" +
+				"perunUserId=" + perunUserId +
+				", sub='" + sub + '\'' +
+				'}';
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimUtils.java
new file mode 100644
index 000000000..b24dafa18
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/ClaimUtils.java
@@ -0,0 +1,38 @@
+package cz.muni.ics.oidc.server.claims;
+
+import org.springframework.util.StringUtils;
+
+public class ClaimUtils {
+
+    public static final String NO_VALUE = null;
+
+    public static boolean isPropSetAndHasNonNullAttribute(String propertyName, ClaimSourceProduceContext ctx) {
+        return isPropSetAndHasAttribute(propertyName, ctx)
+                && ctx.getAttrValues().get(propertyName) != null;
+    }
+
+    public static boolean isPropSetAndHasAttribute(String propertyName, ClaimSourceProduceContext ctx) {
+        return isPropSet(propertyName) && ctx.getAttrValues().containsKey(propertyName);
+    }
+
+    public static boolean isPropSet(String propertyName) {
+        return StringUtils.hasText(propertyName);
+    }
+
+    public static String fillStringPropertyOrNoVal(String suffix, ClaimSourceInitContext ctx) {
+        return fillStringPropertyOrNoVal(ctx.getProperty(suffix, NO_VALUE));
+    }
+
+    public static String fillStringPropertyOrNoVal(String suffix, ClaimModifierInitContext ctx) {
+        return fillStringPropertyOrNoVal(ctx.getProperty(suffix, NO_VALUE));
+    }
+
+    private static String fillStringPropertyOrNoVal(String prop) {
+        if (StringUtils.hasText(prop)) {
+            return prop;
+        } else {
+            return NO_VALUE;
+        }
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/PerunCustomClaimDefinition.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/PerunCustomClaimDefinition.java
new file mode 100644
index 000000000..0cdee1464
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/PerunCustomClaimDefinition.java
@@ -0,0 +1,71 @@
+package cz.muni.ics.oidc.server.claims;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Keeps definition of a custom user claim.
+ *
+ * Configuration declaring custom claims:
+ * <ul>
+ *     <li><b>custom.claims</b> - coma separated list of names of the claims</li>
+ * </ul>
+ *
+ * Configuration for claim(replace [claimName] with name of the claim): *
+ * <ul>
+ *     <li><b>custom.claim.[claimName].claim</b> - name of the claim</li>
+ *     <li><b>custom.claim.[claimName].scope</b> - scope that needs to be granted to include the claim</li>
+ *     <li><b>custom.claim.[claimName].source.class</b> instance of a class implementing {@link ClaimSource}</li>
+ *     <li><b>custom.claim.[claimName].modifier.class</b> instance of a class implementing {@link ClaimModifier}</li>
+ * </ul>
+ *
+ *
+ * @see ClaimSource
+ * @see ClaimModifier
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunCustomClaimDefinition {
+
+	private static final Logger log = LoggerFactory.getLogger(PerunCustomClaimDefinition.class);
+
+	private final String scope;
+	private final String claim;
+	private final ClaimSource claimSource;
+	private final List<ClaimModifier> claimModifiers = new ArrayList<>();
+
+	public PerunCustomClaimDefinition(String scope,
+									  String claim,
+									  ClaimSource claimSource,
+									  List<ClaimModifier> claimModifiers) {
+		this.scope = scope;
+		this.claim = claim;
+		this.claimSource = claimSource;
+		this.claimModifiers.addAll(claimModifiers);
+		log.debug("initialized scope '{}' with claim '{}', claimSource '{}' and modifiers '{}", scope, claim,
+				(claimSource != null ? claimSource.getClass().getSimpleName() : "none"),
+				(!claimModifiers.isEmpty() ? claimModifiers.stream()
+						.map(cm -> cm.getClass().getSimpleName())
+						.collect(Collectors.joining(",")) : "none")
+		);
+	}
+
+	public String getScope() {
+		return scope;
+	}
+
+	public String getClaim() {
+		return claim;
+	}
+
+	public ClaimSource getClaimSource() {
+		return claimSource;
+	}
+
+	public List<ClaimModifier> getClaimModifiers() {
+		return claimModifiers;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/AppendModifier.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/AppendModifier.java
new file mode 100644
index 000000000..606495397
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/AppendModifier.java
@@ -0,0 +1,46 @@
+package cz.muni.ics.oidc.server.claims.modifiers;
+
+import cz.muni.ics.oidc.server.claims.ClaimModifier;
+import cz.muni.ics.oidc.server.claims.ClaimModifierInitContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Appending modifier. Appends the given text to the claim value.
+ *
+ * Configuration (replace [claimName] with the name of the claim and [modifierName] with the name of modifier):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].modifier.[modifierName].append</b> - string to be appended to the value</li>
+ * </ul>
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+@SuppressWarnings("unused")
+public class AppendModifier extends ClaimModifier {
+
+	private static final Logger log = LoggerFactory.getLogger(AppendModifier.class);
+
+	private static final String APPEND = "append";
+
+	private final String appendText;
+
+	public AppendModifier(ClaimModifierInitContext ctx) {
+		super(ctx);
+		appendText = ctx.getProperty(APPEND, "");
+		log.debug("{}(modifier) - appendText: '{}'", getUnifiedName(), appendText);
+	}
+
+	@Override
+	public String modify(String value) {
+		String modified = value + appendText;
+		log.trace("{} - modifying value '{}' by appending text '{}'", getUnifiedName(), value, appendText);
+		log.trace("{} - new value: '{}", getUnifiedName(), modified);
+		return modified;
+	}
+
+	@Override
+	public String toString() {
+		return getUnifiedName() + " - AppendModifier appending " + appendText;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/GroupNamesAARCFormatModifier.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/GroupNamesAARCFormatModifier.java
new file mode 100644
index 000000000..19586a106
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/GroupNamesAARCFormatModifier.java
@@ -0,0 +1,64 @@
+package cz.muni.ics.oidc.server.claims.modifiers;
+
+import com.google.common.net.UrlEscapers;
+import cz.muni.ics.oidc.server.claims.ClaimModifier;
+import cz.muni.ics.oidc.server.claims.ClaimModifierInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * GroupName to AARC Format modifier. Converts groupName values to AARC format.
+ * Construction: prefix:URL_ENCODED_VALUE#authority
+ * Example: urn:geant:cesnet.cz:group:some%20value#perun.cesnet.cz
+ *
+ * Configuration (replace [claimName] with the name of the claim and [modifierName] with the name of modifier):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].modifier.[modifierName].prefix</b> - string to be prepended to the value,
+ *         defaults to <i>urn:geant:cesnet.cz:group:</i>
+ *     </li>
+ *     <li><b>custom.claim.[claimName].modifier.[modifierName].authority</b> - string to be appended to the value,
+ *         represents authority who has released the value, defaults to <i>perun.cesnet.cz</i>
+ *     </li>
+ * </ul>
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+@SuppressWarnings("unused")
+public class GroupNamesAARCFormatModifier extends ClaimModifier {
+
+	private static final Logger log = LoggerFactory.getLogger(GroupNamesAARCFormatModifier.class);
+
+	public static final String PREFIX = "prefix";
+	public static final String AUTHORITY = "authority";
+
+	private final String prefix;
+	private final String authority;
+
+	public GroupNamesAARCFormatModifier(ClaimModifierInitContext ctx) {
+		super(ctx);
+		this.prefix = ClaimUtils.fillStringPropertyOrNoVal(PREFIX, ctx);
+		if (!ClaimUtils.isPropSet(this.prefix)) {
+			throw new IllegalArgumentException(getUnifiedName() + " - missing mandatory configuration option: " + PREFIX);
+		}
+		this.authority = ClaimUtils.fillStringPropertyOrNoVal(AUTHORITY, ctx);
+		if (!ClaimUtils.isPropSet(this.authority)) {
+			throw new IllegalArgumentException(getUnifiedName() + " - missing mandatory configuration option: " + AUTHORITY);
+		}
+		log.debug("{}:{}(modifier) - prefix: '{}', authority: '{}'", getClaimName(), getModifierName(), prefix, authority);
+	}
+
+	@Override
+	public String modify(String value) {
+		String modified = prefix + UrlEscapers.urlPathSegmentEscaper().escape(value) + "#" + authority;
+		log.trace("{} - modifying value '{}' to AARC format", getUnifiedName(), value);
+		log.trace("{} - new value: '{}", getUnifiedName(), modified);
+		return modified;
+	}
+
+	@Override
+	public String toString() {
+		return getUnifiedName() +  " - GroupNamesAARCFormatModifier to " + prefix + "<GROUP>#" + authority;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/NoOperationModifier.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/NoOperationModifier.java
new file mode 100644
index 000000000..6578de050
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/NoOperationModifier.java
@@ -0,0 +1,30 @@
+package cz.muni.ics.oidc.server.claims.modifiers;
+
+import cz.muni.ics.oidc.server.claims.ClaimModifier;
+import cz.muni.ics.oidc.server.claims.ClaimModifierInitContext;
+
+/**
+ * No operation modifier. Class just for consistent working with modifiers to avoid checking for null references.
+ *
+ * No further configuration.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@SuppressWarnings("unused")
+public class NoOperationModifier extends ClaimModifier {
+
+	public NoOperationModifier(ClaimModifierInitContext ctx) {
+		super(ctx);
+	}
+
+	@Override
+	public String modify(String value) {
+		return value;
+	}
+
+	@Override
+	public String toString() {
+		return getUnifiedName() + " - No operation modifier";
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/RegexReplaceModifier.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/RegexReplaceModifier.java
new file mode 100644
index 000000000..089186d9c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/modifiers/RegexReplaceModifier.java
@@ -0,0 +1,53 @@
+package cz.muni.ics.oidc.server.claims.modifiers;
+
+import cz.muni.ics.oidc.server.claims.ClaimModifier;
+import cz.muni.ics.oidc.server.claims.ClaimModifierInitContext;
+import java.util.regex.Pattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Replace regex modifier. Replaces parts matched by regex with string using backreferences to groups.
+ *
+ * Configuration (replace [claimName] with the name of the claim and [modifierName] with the name of modifier):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].modifier.[modifierName].find</b> - string to be replaced, can be a regex</li>
+ *     <li><b>custom.claim.[claimName].modifier.[modifierName].replace</b> - string to be used as replacement</li>
+ * </ul>
+ *
+ * @see java.util.regex.Matcher#replaceAll(String)
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+@SuppressWarnings("unused")
+public class RegexReplaceModifier extends ClaimModifier {
+
+	private static final Logger log = LoggerFactory.getLogger(RegexReplaceModifier.class);
+
+	private static final String FIND = "find";
+	private static final String REPLACE = "replace";
+
+	private final Pattern regex;
+	private final String replacement;
+
+	public RegexReplaceModifier(ClaimModifierInitContext ctx) {
+		super(ctx);
+		regex = Pattern.compile(ctx.getProperty(FIND, ""));
+		replacement = ctx.getProperty(REPLACE, "");
+		log.debug("{}(modifier) - regex: '{}', replacement: '{}'", getUnifiedName(), regex, replacement);
+	}
+
+	@Override
+	public String modify(String value) {
+		String modified = regex.matcher(value).replaceAll(replacement);
+		log.trace("{} - modifying value '{}' by replacing matched part ('{}') with: '{}'", getUnifiedName(),
+				value, regex, replacement);
+		log.trace("{} - new value: '{}", getUnifiedName(), modified);
+		return modified;
+	}
+
+	@Override
+	public String toString() {
+		return getUnifiedName() + " - RegexReplaceModifier replacing '" + regex.pattern()
+				+ "' with '" + replacement + '\'';
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EdupersonScopedAffiliationsMUSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EdupersonScopedAffiliationsMUSource.java
new file mode 100644
index 000000000..e0ea37466
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EdupersonScopedAffiliationsMUSource.java
@@ -0,0 +1,112 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Claim source for eduperson_scoped_affiliations MUNI.
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ * <ul>
+ *     <li>
+ *         <b>custom.claim.[claimName].source.config_file</b> - path to the YML config file, see
+ *         'eduperson_scoped_affiliations_mu_source.yml' for example configuration
+ *     </li>
+ * </ul>
+ *
+ * @author Dominik Baránek <baranek@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class EdupersonScopedAffiliationsMUSource extends ClaimSource {
+
+	private static final Logger log = LoggerFactory.getLogger(EdupersonScopedAffiliationsMUSource.class);
+
+	private static final String CONFIG_FILE = "config_file";
+	private static final String KEY_SCOPE = "scope";
+	private static final String KEY_VO_ID = "voId";
+	private static final String KEY_AFFILIATIONS = "affiliations";
+	private static final String KEY_VALUE = "value";
+	private static final String KEY_GROUPS = "groups";
+
+	private static final String DEFAULT_PATH = "/etc/perun/eduperson_scoped_affiliations_mu_source.yml";
+
+	private final Map<List<Long>, String> affiliations = new HashMap<>();
+	private Long voId = 363L;
+	private String valueScope = "muni.cz";
+
+	public EdupersonScopedAffiliationsMUSource(ClaimSourceInitContext ctx) {
+		super(ctx);
+		parseConfigFile(ctx.getProperty(CONFIG_FILE, DEFAULT_PATH));
+		log.debug("{} - affiliations: '{}', voId: '{}', valueScope: '{}'",
+				getClaimName(), affiliations, voId, valueScope);
+	}
+
+	@Override
+	public Set<String> getAttrIdentifiers() {
+		return Collections.emptySet();
+	}
+
+	@Override
+	public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+		Long userId = pctx.getPerunUserId();
+		ArrayNode result = JsonNodeFactory.instance.arrayNode();
+		Set<Long> groups = pctx.getPerunAdapter().getUserGroupsIds(userId, voId);
+		for (Map.Entry<List<Long>, String> entry : affiliations.entrySet()) {
+			for (Long id: entry.getKey()) {
+				if (groups.contains(id)) {
+					String affiliation = entry.getValue() + '@' + valueScope;
+					log.trace("{} - added affiliation '{}' due to membership in group '{}'",
+							getClaimName(), affiliation, id);
+					result.add(affiliation);
+					break;
+				}
+			}
+		}
+
+		if (result.size() == 0) {
+			String affiliation = "affiliate@" + valueScope;
+			log.trace("{} - user is not a member in any special groups, added default affiliation: '{}'",
+					getClaimName(), affiliation);
+			result.add(affiliation);
+		}
+
+		log.debug("{} - produced value for user({}): '{}'", getClaimName(), userId, result);
+		return result;
+	}
+
+	private void parseConfigFile(String file) {
+		log.trace("{} - Loading config file {}", getClaimName(), file);
+		YAMLMapper mapper = new YAMLMapper();
+		try {
+			JsonNode root = mapper.readValue(new File(file), JsonNode.class);
+			valueScope = root.get(KEY_SCOPE).asText();
+			voId = root.get(KEY_VO_ID).longValue();
+			for (JsonNode affiliationMapping : root.path(KEY_AFFILIATIONS)) {
+				String value = affiliationMapping.path(KEY_VALUE).asText();
+				List<Long> gids = new ArrayList<>();
+				for (JsonNode gid : affiliationMapping.path(KEY_GROUPS)) {
+					gids.add(gid.asLong());
+				}
+				affiliations.put(gids, value);
+			}
+		} catch (IOException ex) {
+			log.warn("{} - cannot read claim configuration file: '{}'", getClaimName(), file);
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EntitlementExtendedClaimSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EntitlementExtendedClaimSource.java
new file mode 100644
index 000000000..4c1017bad
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EntitlementExtendedClaimSource.java
@@ -0,0 +1,82 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+import org.yaml.snakeyaml.external.com.google.gdata.util.common.base.PercentEscaper;
+
+public class EntitlementExtendedClaimSource extends EntitlementSource {
+
+    public static final Logger log = LoggerFactory.getLogger(EntitlementExtendedClaimSource.class);
+
+    private static final String GROUP = "group";
+    private static final String GROUP_ATTRIBUTES = "groupAttributes";
+    private static final String DISPLAY_NAME = "displayName";
+
+    private static final PercentEscaper ESCAPER = new PercentEscaper("-_.!~*'()", false);
+
+    public EntitlementExtendedClaimSource(ClaimSourceInitContext ctx) {
+        super(ctx);
+        log.debug("{} - initialized", getClaimName());
+    }
+
+    @Override
+    public Set<String> getAttrIdentifiers() {
+        return super.getAttrIdentifiers();
+    }
+
+    @Override
+    public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+        Long userId = pctx.getPerunUserId();
+        Set<String> entitlements = produceEntitlementsExtended(pctx.getContextCommonParameters().getClient(),
+                userId, pctx.getPerunAdapter());
+        JsonNode result = convertResultStringsToJsonArray(entitlements);
+        log.debug("{} - produced value for user({}): '{}'", getClaimName(), userId, result);
+        return result;
+    }
+
+    private Set<String> produceEntitlementsExtended(Facility facility, Long userId, PerunAdapter perunAdapter) {
+        Set<Group> userGroups = getUserGroupsOnFacility(facility, userId, perunAdapter);
+        Map<Long, String> groupIdToNameMap = super.getGroupIdToNameMap(userGroups, false);
+        Set<String> entitlements = new TreeSet<>();
+        this.fillUuidEntitlements(userGroups, entitlements);
+        fillForwardedEntitlements(perunAdapter, userId, entitlements);
+        fillCapabilities(facility, perunAdapter, groupIdToNameMap,entitlements);
+        log.trace("{} - UUID entitlements added", getClaimName());
+        return entitlements;
+    }
+
+    private void fillUuidEntitlements(Set<Group> userGroups, Set<String> entitlements) {
+        for (Group group : userGroups) {
+            String displayName = group.getUniqueGroupName();
+            if (StringUtils.hasText(displayName) && MEMBERS.equals(group.getName())) {
+                displayName = displayName.replace(':' + MEMBERS, "");
+            }
+            String entitlement = wrapGroupEntitlementToAARC(group.getUuid());
+            log.trace("{} - added UUID entitlement: '{}'", getClaimName(), entitlement);
+            entitlements.add(entitlement);
+            String entitlementWithAttributes = wrapGroupEntitlementToAARCWithAttributes(group.getUuid(), displayName);
+            log.trace("{} - added UUID entitlement with displayName: '{}'", getClaimName(), entitlementWithAttributes);
+            entitlements.add(entitlementWithAttributes);
+        }
+    }
+
+    private String wrapGroupEntitlementToAARC(String uuid) {
+        return addPrefixAndSuffix(GROUP + ':' + uuid);
+    }
+
+    private String wrapGroupEntitlementToAARCWithAttributes(String uuid, String displayName) {
+        return addPrefixAndSuffix(GROUP_ATTRIBUTES + ':' + uuid + "?=" + DISPLAY_NAME + '=' +
+                ESCAPER.escape(displayName));
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EntitlementSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EntitlementSource.java
new file mode 100644
index 000000000..2e8432b1c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/EntitlementSource.java
@@ -0,0 +1,185 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.UrlEscapers;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import cz.muni.ics.oidc.server.claims.ClaimUtils;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * This source converts groupNames and resource capabilities to AARC format and joins them with eduPersonEntitlement.
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ * <ul>
+ *     <li>
+ *         <b>custom.claim.[claimName].source.forwardedEntitlements</b> - forwardedEntitlements attribute name,
+ *         if not specified, the forwarded entitlements will not be added to the list
+ *     </li>
+ *     <li><b>custom.claim.[claimName].source.resourceCapabilities</b> - resource capabilities attribute name for resources</li>
+ *     <li><b>custom.claim.[claimName].source.facilityCapabilities</b> - resource capabilities attribute name for facility</li>
+ *     <li><b>custom.claim.[claimName].source.prefix</b> - string to be prepended to the value,</li>
+ *     <li>
+ *         <b>custom.claim.[claimName].source.authority</b> - string to be appended to the value, represents authority
+ *         who has released the value
+ *     </li>
+ * </ul>
+ *
+ * @author Dominik Baránek <baranek@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class EntitlementSource extends GroupNamesSource {
+
+	public static final Logger log = LoggerFactory.getLogger(EntitlementSource.class);
+
+	private static final String GROUP = "group";
+
+	protected static final String FORWARDED_ENTITLEMENTS = "forwardedEntitlements";
+	protected static final String RESOURCE_CAPABILITIES = "resourceCapabilities";
+	protected static final String FACILITY_CAPABILITIES = "facilityCapabilities";
+	protected static final String PREFIX = "prefix";
+	protected static final String AUTHORITY = "authority";
+
+	private final String forwardedEntitlements;
+	private final String resourceCapabilities;
+	private final String facilityCapabilities;
+	private final String prefix;
+	private final String authority;
+
+	public EntitlementSource(ClaimSourceInitContext ctx) {
+		super(ctx);
+		this.forwardedEntitlements = ClaimUtils.fillStringPropertyOrNoVal(FORWARDED_ENTITLEMENTS, ctx);
+		this.resourceCapabilities = ClaimUtils.fillStringPropertyOrNoVal(RESOURCE_CAPABILITIES, ctx);
+		this.facilityCapabilities = ClaimUtils.fillStringPropertyOrNoVal(FACILITY_CAPABILITIES, ctx);
+		this.prefix = ClaimUtils.fillStringPropertyOrNoVal(PREFIX, ctx);
+		if (!ClaimUtils.isPropSet(this.prefix)) {
+			throw new IllegalArgumentException(getClaimName() + " - missing mandatory configuration option: " +
+					PREFIX);
+		}
+		this.authority = ClaimUtils.fillStringPropertyOrNoVal(AUTHORITY, ctx);
+		if (!ClaimUtils.isPropSet(this.authority)) {
+			throw new IllegalArgumentException(getClaimName() + " - missing mandatory configuration option: " +
+					AUTHORITY);
+		}
+		log.debug("{} - forwardedEntitlements: '{}', resourceCapabilities: '{}', facilityCapabilities: '{}', " +
+				"prefix: '{}', authority: '{}'", getClaimName(), forwardedEntitlements, resourceCapabilities,
+				facilityCapabilities, prefix, authority);
+	}
+
+	@Override
+	public Set<String> getAttrIdentifiers() {
+		Set<String> set = new HashSet<>(super.getAttrIdentifiers());
+		if (forwardedEntitlements != null) {
+			set.add(forwardedEntitlements);
+		}
+		return set;
+	}
+
+	@Override
+	public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+		PerunAdapter perunAdapter = pctx.getPerunAdapter();
+		Long userId = pctx.getPerunUserId();
+		Facility facility = pctx.getContextCommonParameters().getClient();
+		Set<Group> userGroups = getUserGroupsOnFacility(facility, userId, perunAdapter);
+		Set<String> entitlements = produceEntitlements(facility, userGroups, userId, perunAdapter);
+
+		JsonNode result = convertResultStringsToJsonArray(entitlements);
+		log.debug("{} - produced value for user({}): '{}'", getClaimName(), userId, result);
+		return result;
+	}
+
+	protected void fillCapabilities(Facility facility, PerunAdapter perunAdapter,
+								  Map<Long, String> idToGnameMap, Set<String> entitlements) {
+		Set<String> resultCapabilities = perunAdapter
+				.getCapabilities(facility, idToGnameMap,
+						ClaimUtils.isPropSet(this.facilityCapabilities) ? facilityCapabilities : null,
+						ClaimUtils.isPropSet(this.resourceCapabilities)? resourceCapabilities: null);
+
+		for (String capability : resultCapabilities) {
+			entitlements.add(wrapCapabilityToAARC(capability));
+			log.trace("{} - added capability: {}", getClaimName(), capability);
+		}
+	}
+
+	protected void fillForwardedEntitlements(PerunAdapter perunAdapter, Long userId, Set<String> entitlements) {
+		PerunAttributeValue forwardedEntitlementsVal = perunAdapter
+				.getUserAttributeValue(userId, this.forwardedEntitlements);
+		if (forwardedEntitlementsVal != null && !forwardedEntitlementsVal.isNullValue()) {
+			JsonNode eduPersonEntitlementJson = forwardedEntitlementsVal.valueAsJson();
+			for (int i = 0; i < eduPersonEntitlementJson.size(); i++) {
+				String entitlement = eduPersonEntitlementJson.get(i).asText();
+				log.trace("{} - added forwarded entitlement: {}", getClaimName(), entitlement);
+				entitlements.add(entitlement);
+			}
+		}
+	}
+
+	private void fillEntitlementsFromGroupNames(Collection<String> groupNames, Set<String> entitlements) {
+		for (String fullGname: groupNames) {
+			if (fullGname == null || fullGname.trim().isEmpty()) {
+				continue;
+			}
+
+			String[] parts = fullGname.split(":", 2);
+			if (parts.length == 2 && StringUtils.hasText(parts[1]) && MEMBERS.equals(parts[1])) {
+				parts[1] = parts[1].replace(MEMBERS, "");
+			}
+
+			String gname = parts[0];
+			if (StringUtils.hasText(parts[1])) {
+				gname += (':' + parts[1]);
+			}
+			String gNameEntitlement = wrapGroupNameToAARC(gname);
+			log.trace("{} - added group name entitlement: {}", getClaimName(), gNameEntitlement);
+			entitlements.add(gNameEntitlement);
+		}
+	}
+
+	protected Set<String> produceEntitlements(Facility facility, Set<Group> userGroups,
+											  Long userId, PerunAdapter perunAdapter)
+	{
+		Set<String> entitlements = new TreeSet<>();
+		Map<Long, String> groupIdToNameMap = super.getGroupIdToNameMap(userGroups, false);
+
+		if (groupIdToNameMap != null && !groupIdToNameMap.values().isEmpty()) {
+			this.fillEntitlementsFromGroupNames(new HashSet<>(groupIdToNameMap.values()), entitlements);
+			log.trace("{} - entitlements for group names added", getClaimName());
+		}
+
+		if (facility != null) {
+			this.fillCapabilities(facility, perunAdapter, groupIdToNameMap, entitlements);
+			log.trace("{} - capabilities added", getClaimName());
+		}
+
+		if (ClaimUtils.isPropSet(this.forwardedEntitlements)) {
+			this.fillForwardedEntitlements(perunAdapter, userId, entitlements);
+			log.trace("{} - forwarded entitlements added", getClaimName());
+		}
+
+		return entitlements;
+	}
+
+	protected String wrapGroupNameToAARC(String groupName) {
+		return addPrefixAndSuffix(GROUP + ':' + UrlEscapers.urlPathSegmentEscaper().escape(groupName));
+	}
+
+	private String wrapCapabilityToAARC(String capability) {
+		return addPrefixAndSuffix(UrlEscapers.urlPathSegmentEscaper().escape(capability));
+	}
+
+	protected String addPrefixAndSuffix(String capability) {
+		return prefix + capability + '#' + authority;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/ExtractValuesByDomainSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/ExtractValuesByDomainSource.java
new file mode 100644
index 000000000..4ffc3b38f
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/ExtractValuesByDomainSource.java
@@ -0,0 +1,99 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import cz.muni.ics.oidc.server.claims.ClaimUtils;
+import java.util.Collections;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This source extract attribute values for given scope
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].source.extractByDomain</b> - domain which should be matched</li>
+ *     <li><b>custom.claim.[claimName].source.attributeName</b> - attribute in which the lookup should be performed</li>
+ * </ul>
+ *
+ * @author Dominik Baránek <baranek@ics.muni.cz>
+ */
+public class ExtractValuesByDomainSource extends ClaimSource {
+
+	private static final Logger log = LoggerFactory.getLogger(ExtractValuesByDomainSource.class);
+
+	private static final String EXTRACT_BY_DOMAIN = "extractByDomain";
+	private static final String ATTRIBUTE_NAME = "attributeName";
+
+	private final String domain;
+	private final String attributeName;
+
+	public ExtractValuesByDomainSource(ClaimSourceInitContext ctx) {
+		super(ctx);
+		this.domain = ClaimUtils.fillStringPropertyOrNoVal(EXTRACT_BY_DOMAIN, ctx);
+		if (!ClaimUtils.isPropSet(this.domain)) {
+			throw new IllegalArgumentException(getClaimName() + " - missing mandatory configuration option: "
+					+ EXTRACT_BY_DOMAIN);
+		}
+		this.attributeName = ClaimUtils.fillStringPropertyOrNoVal(ATTRIBUTE_NAME, ctx);
+		if (!ClaimUtils.isPropSet(this.attributeName)) {
+			throw new IllegalArgumentException(getClaimName() + " - missing mandatory configuration option: "
+					+ ATTRIBUTE_NAME);
+		}
+		log.debug("{} - domain: '{}', attributeName: '{}'", getClaimName(), domain, attributeName);
+	}
+
+	@Override
+	public Set<String> getAttrIdentifiers() {
+		return Collections.singleton(attributeName);
+	}
+
+	@Override
+	public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+		JsonNode result = NullNode.getInstance();
+		if (!ClaimUtils.isPropSet(domain)) {
+			log.trace("{} - no domain set, return empty JSON", domain);
+			result = NullNode.getInstance();
+		} else if (!ClaimUtils.isPropSetAndHasAttribute(attributeName, pctx)) {
+			log.trace("{} - no attributeName set, return empty JSON", domain);
+			result = NullNode.getInstance();
+		} else {
+			PerunAttributeValue attributeValue = pctx.getAttrValues().get(attributeName);
+			if (attributeValue != null) {
+				JsonNode attributeValueJson = attributeValue.valueAsJson();
+				if (attributeValueJson.isTextual() && hasDomain(attributeValueJson.textValue(), domain)) {
+					log.trace("{} - found domain in string value: '{}'", getClaimName(), attributeValueJson);
+					result = attributeValueJson;
+				} else if (attributeValueJson.isArray()) {
+					ArrayNode arrayNode = (ArrayNode) attributeValueJson;
+					JsonNodeFactory factory = JsonNodeFactory.instance;
+					ArrayNode arr = new ArrayNode(factory);
+
+					for (int i = 0; i < arrayNode.size(); i++) {
+						String subValue = arrayNode.get(i).textValue();
+						if (hasDomain(subValue, domain)) {
+							log.trace("{} - found domain in array sub-value: '{}'", getClaimName(), subValue);
+							arr.add(subValue);
+						}
+					}
+					result = arr;
+				}
+			}
+		}
+		log.debug("{} - produced value for user({}): '{}'", getClaimName(), pctx.getPerunUserId(), result);
+		return result;
+	}
+
+	private boolean hasDomain(String value, String domain) {
+		String[] parts = value.split("@");
+		return parts[parts.length - 1].equals(domain);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/GroupNamesSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/GroupNamesSource.java
new file mode 100644
index 000000000..fe090bb2c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/GroupNamesSource.java
@@ -0,0 +1,92 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * Source fetches all unique group names in context of user and facility. If no facility exists for the client, empty
+ * list is returned as result.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class GroupNamesSource extends ClaimSource {
+
+	public static final Logger log = LoggerFactory.getLogger(GroupNamesSource.class);
+
+	protected static final String MEMBERS = "members";
+
+	public GroupNamesSource(ClaimSourceInitContext ctx) {
+		super(ctx);
+		log.debug("{} - initialized", getClaimName());
+	}
+
+	@Override
+	public Set<String> getAttrIdentifiers() {
+		return Collections.emptySet();
+	}
+
+	@Override
+	public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+		Map<Long, String> idToNameMap = this.produceGroupNames(pctx);
+		JsonNode result = convertResultStringsToJsonArray(new HashSet<>(idToNameMap.values()));
+		log.debug("{} - produced value for user({}): '{}'", getClaimName(), pctx.getPerunUserId(), result);
+		return result;
+	}
+
+	protected Map<Long, String> produceGroupNames(ClaimSourceProduceContext pctx) {
+		log.trace("{} - produce group names with trimming 'members' part of the group names", getClaimName());
+		Facility facility = pctx.getContextCommonParameters().getClient();
+		Set<Group> userGroups = getUserGroupsOnFacility(facility, pctx.getPerunUserId(), pctx.getPerunAdapter());
+		return getGroupIdToNameMap(userGroups, true);
+	}
+
+	protected Map<Long, String> getGroupIdToNameMap(Set<Group> userGroups, boolean trimMembers) {
+		Map<Long, String> idToNameMap = new HashMap<>();
+		userGroups.forEach(g -> {
+			String uniqueName = g.getUniqueGroupName();
+			if (trimMembers && StringUtils.hasText(uniqueName) && MEMBERS.equals(g.getName())) {
+				uniqueName = uniqueName.replace(':' + MEMBERS, "");
+				g.setUniqueGroupName(uniqueName);
+			}
+
+			idToNameMap.put(g.getId(), g.getUniqueGroupName());
+		});
+
+		log.trace("{} - group ID to group name map: '{}'", getClaimName(), idToNameMap);
+		return idToNameMap;
+	}
+
+	protected Set<Group> getUserGroupsOnFacility(Facility facility, Long userId, PerunAdapter perunAdapter) {
+		Set<Group> userGroups = new HashSet<>();
+		if (facility == null) {
+			log.warn("{} - no facility provided when searching for user groups, will return empty set", getClaimName());
+		} else {
+			userGroups = perunAdapter.getGroupsWhereUserIsActiveWithUniqueNames(facility.getId(), userId);
+		}
+		log.trace("{} - found user groups: '{}'", getClaimName(), userGroups);
+		return userGroups;
+	}
+
+	protected JsonNode convertResultStringsToJsonArray(Collection<String> collection) {
+		ArrayNode arr = JsonNodeFactory.instance.arrayNode();
+		collection.forEach(arr::add);
+		return arr;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/IsCesnetEligibleClaimSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/IsCesnetEligibleClaimSource.java
new file mode 100644
index 000000000..0992bced4
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/IsCesnetEligibleClaimSource.java
@@ -0,0 +1,100 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import cz.muni.ics.oidc.server.claims.ClaimUtils;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Collections;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * This source checks if the timestamp is within 12 months from now. If so, returns TRUE, FALSE otherwise.
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].source.attribute</b> - attribute containing the isCesnetEligible timestamp</li>
+ *     <li><b>custom.claim.[claimName].source.valueFormat</b> - format of the value (i.e. yyyy-MM-dd HH:mm:ss</li>
+ * </ul>
+ *
+ * @author Pavol Pluta <pavol.pluta1@gmail.com>
+ */
+public class IsCesnetEligibleClaimSource extends ClaimSource {
+
+    public static final Logger log = LoggerFactory.getLogger(IsCesnetEligibleClaimSource.class);
+
+    private static final String DEFAULT_FORMAT = "yyyy-MM-dd HH:mm:ss";
+    private static final int VALIDITY_PERIOD = 12; // 12 months
+
+    private static final String SOURCE_ATTR_NAME = "attribute";
+    private static final String VALUE_FORMAT = "valueFormat";
+
+    private final String sourceAttr;
+    private String valueFormat;
+
+    public IsCesnetEligibleClaimSource(ClaimSourceInitContext ctx) {
+        super(ctx);
+        this.sourceAttr = ClaimUtils.fillStringPropertyOrNoVal(SOURCE_ATTR_NAME, ctx);
+        if (!ClaimUtils.isPropSet(sourceAttr)) {
+            throw new IllegalArgumentException(getClaimName() + " - missing mandatory configuration option: " +
+                    SOURCE_ATTR_NAME);
+        }
+        this.valueFormat = ClaimUtils.fillStringPropertyOrNoVal(VALUE_FORMAT, ctx);
+        if (!ClaimUtils.isPropSet(valueFormat)) {
+            this.valueFormat = DEFAULT_FORMAT;
+        }
+        log.debug("{} - sourceAttr: '{}', valueFormat: '{}'", getClaimName(), sourceAttr, valueFormat);
+    }
+
+    @Override
+    public Set<String> getAttrIdentifiers() {
+        return Collections.singleton(sourceAttr);
+    }
+
+    @Override
+    public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+        JsonNode result;
+        if (ClaimUtils.isPropSetAndHasAttribute(sourceAttr, pctx)) {
+            String lastSeen = pctx.getAttrValues().get(sourceAttr).valueAsString();
+            result = JsonNodeFactory.instance.booleanNode(this.isCesnetEligible(lastSeen));
+        } else {
+            result = JsonNodeFactory.instance.booleanNode(false);
+        }
+        log.debug("{} - produced value for user({}): '{}'", getClaimName(), pctx.getPerunUserId(), result);
+        return result;
+    }
+
+    private boolean isCesnetEligible(String attrValue) {
+        if (!StringUtils.hasText(attrValue)) {
+            return false;
+        }
+        LocalDate timeStampLastSeen;
+        try {
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(valueFormat);
+            timeStampLastSeen = LocalDate.parse(attrValue, formatter);
+        } catch (DateTimeParseException e) {
+            log.warn("{} - could not parse timestamp (for format: {}) value: '{}'", getClaimName(), valueFormat, attrValue);
+            return false;
+        }
+
+        LocalDate now = LocalDateTime.now().toLocalDate();
+        if (timeStampLastSeen.isBefore(now.minusMonths(VALIDITY_PERIOD))) {
+            log.trace("{} - timestamp '{}' is after the defined period of '{} months'",
+                    getClaimName(), timeStampLastSeen, VALIDITY_PERIOD);
+            return false;
+        } else {
+            log.trace("{} - timestamp '{}' is within the defined period of '{} months'",
+                    getClaimName(), timeStampLastSeen, VALIDITY_PERIOD);
+            return true;
+        }
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/PerunAttributeClaimSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/PerunAttributeClaimSource.java
new file mode 100644
index 000000000..ef5b7d59c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/PerunAttributeClaimSource.java
@@ -0,0 +1,64 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NullNode;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import cz.muni.ics.oidc.server.claims.ClaimUtils;
+import java.util.Collections;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Source for claim which get value of attribute from Perun.
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].source.attribute</b> - name of the attribute in Perun</li>
+ * </ul>
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@SuppressWarnings("unused")
+public class PerunAttributeClaimSource extends ClaimSource {
+
+	public static final Logger log = LoggerFactory.getLogger(PerunAttributeClaimSource.class);
+
+	private static final String ATTRIBUTE = "attribute";
+
+	private final String attributeName;
+
+	public PerunAttributeClaimSource(ClaimSourceInitContext ctx) {
+		super(ctx);
+		this.attributeName = ClaimUtils.fillStringPropertyOrNoVal(ATTRIBUTE, ctx);
+		if (!ClaimUtils.isPropSet(this.attributeName)) {
+			throw new IllegalArgumentException("Missing mandatory configuration option - " + ATTRIBUTE);
+		}
+		log.debug("{} - attributeName: '{}'", getClaimName(), attributeName);
+	}
+
+	@Override
+	public Set<String> getAttrIdentifiers() {
+		return Collections.singleton(attributeName);
+	}
+
+	@Override
+	public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+		JsonNode value = NullNode.getInstance();
+		if (ClaimUtils.isPropSetAndHasAttribute(attributeName, pctx)) {
+			value = pctx.getAttrValues().get(attributeName).valueAsJson();
+		}
+
+		log.debug("{} - produced value for user({}): '{}'", getClaimName(), pctx.getPerunUserId(), value);
+		return value;
+	}
+
+	@Override
+	public String toString() {
+		return "Perun attribute " + attributeName;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/StaticValueClaimSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/StaticValueClaimSource.java
new file mode 100644
index 000000000..59e88d140
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/StaticValueClaimSource.java
@@ -0,0 +1,87 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.NullNode;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * Source for claim which releases the defined value.
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].source.valueSeparator</b> - @NULL or actual separator, if set to smth. else than null,
+ *     value is considered as an array</li>
+ *     <li><b>custom.claim.[claimName].source.value</b> - list of values separated by specified separator, in case
+ *     of string separator should be set to @NULL and full value will be released as string</li>
+ * </ul>
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@SuppressWarnings("unused")
+public class StaticValueClaimSource extends ClaimSource {
+
+	public static final Logger log = LoggerFactory.getLogger(StaticValueClaimSource.class);
+
+	private static final String NO_SEPARATOR = "@NULL";
+	private static final String VALUE_SEPARATOR = "valueSeparator";
+	private static final String VALUE = "value";
+
+	private final String valueSeparator;
+	private String[] valueArr;
+	private final String valueStr;
+
+	public StaticValueClaimSource(ClaimSourceInitContext ctx) {
+		super(ctx);
+		this.valueSeparator = ctx.getProperty(VALUE_SEPARATOR, NO_SEPARATOR);
+		this.valueStr = ctx.getProperty(VALUE, null);
+		this.valueArr = null;
+		if (valueStr != null) {
+			valueArr = valueStr.split(valueSeparator);
+		}
+		log.debug("{} - valueSeparator: '{}', valueStr: '{}', valueArr: '{}'", getClaimName(),
+				valueSeparator, valueStr, valueArr);
+	}
+
+	@Override
+	public Set<String> getAttrIdentifiers() {
+		return Collections.emptySet();
+	}
+
+	@Override
+	public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+		JsonNode value = NullNode.getInstance();
+		if (!NO_SEPARATOR.equals(valueSeparator) && valueArr != null) {
+			ArrayNode arrJson = JsonNodeFactory.instance.arrayNode();
+			for (String v: valueArr) {
+				if (StringUtils.hasText(v)) {
+					arrJson.add(v);
+				}
+			}
+			if (arrJson.size() > 0) {
+				value = arrJson;
+			}
+		} else if (StringUtils.hasText(valueStr)) {
+			value = JsonNodeFactory.instance.textNode(valueStr);
+		}
+
+		log.debug("{} - produced value for user({}): '{}'", getClaimName(), pctx.getPerunUserId(), value);
+		return value;
+	}
+
+	@Override
+	public String toString() {
+		return "Fixed value " + (valueArr != null ? Arrays.toString(valueArr) : valueStr);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/TwoArrayAttributesClaimSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/TwoArrayAttributesClaimSource.java
new file mode 100644
index 000000000..8f6c279b3
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/claims/sources/TwoArrayAttributesClaimSource.java
@@ -0,0 +1,90 @@
+package cz.muni.ics.oidc.server.claims.sources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import cz.muni.ics.oidc.server.claims.ClaimUtils;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Claim source which takes value from two attributes from Perun.
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].source.attribute1</b> - name of the first attribute in Perun</li>
+ *     <li><b>custom.claim.[claimName].source.attribute2</b> - name of the second attribute in Perun</li>
+ * </ul>
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@SuppressWarnings("unused")
+public class TwoArrayAttributesClaimSource extends ClaimSource {
+
+	private static final Logger log = LoggerFactory.getLogger(TwoArrayAttributesClaimSource.class);
+
+	public static final String ATTRIBUTE_1 = "attribute1";
+	public static final String ATTRIBUTE_2 = "attribute2";
+
+	private final String attribute1Name;
+	private final String attribute2Name;
+
+	public TwoArrayAttributesClaimSource(ClaimSourceInitContext ctx) {
+		super(ctx);
+		this.attribute1Name = ClaimUtils.fillStringPropertyOrNoVal(ATTRIBUTE_1, ctx);
+		if (!ClaimUtils.isPropSet(this.attribute1Name)) {
+			throw new IllegalArgumentException(getClaimName() + " - missing mandatory configuration option: " +
+					ATTRIBUTE_1);
+		}
+		this.attribute2Name = ClaimUtils.fillStringPropertyOrNoVal(ATTRIBUTE_2, ctx);
+		if (!ClaimUtils.isPropSet(this.attribute2Name)) {
+			throw new IllegalArgumentException(getClaimName() + " - missing mandatory configuration option: " +
+					ATTRIBUTE_2);
+		}
+		log.debug("{} - attribute1Name: '{}', attribute2Name: '{}'", getClaimName(), attribute1Name, attribute2Name);
+	}
+
+	@Override
+	public Set<String> getAttrIdentifiers() {
+		return new HashSet<>(Arrays.asList(attribute1Name, attribute1Name));
+	}
+
+	@Override
+	public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+		JsonNode j1 = new ArrayNode(JsonNodeFactory.instance);
+		if (ClaimUtils.isPropSetAndHasAttribute(attribute1Name, pctx)) {
+			j1 = pctx.getAttrValues().get(attribute1Name).valueAsJson();
+		}
+		log.trace("{} - found values for '{}': {}", getClaimName(), attribute1Name, j1);
+
+		JsonNode j2 = new ArrayNode(JsonNodeFactory.instance);
+		if (ClaimUtils.isPropSetAndHasAttribute(attribute2Name, pctx)) {
+			j2 = pctx.getAttrValues().get(attribute2Name).valueAsJson();
+		}
+		log.trace("{} - found values for '{}': {}", getClaimName(), attribute2Name, j2);
+
+		JsonNode result;
+		if (j1 == null || j1.isNull() || !j1.isArray()) {
+			result = j2;
+		}  else if (j2 == null || j2.isNull() || !j2.isArray()) {
+			result = j1;
+		} else {
+			ArrayNode a1 = (ArrayNode) j1;
+			ArrayNode a2 = (ArrayNode) j2;
+			ArrayNode arr = a1.arrayNode(a1.size() + a2.size());
+			arr.addAll(a1);
+			arr.addAll(a2);
+			result = arr;
+		}
+		log.debug("{} - produced value for user({}): '{}'", getClaimName(), pctx.getPerunUserId(), result);
+		return result;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/configurations/FacilityAttrsConfig.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/configurations/FacilityAttrsConfig.java
new file mode 100644
index 000000000..68753663e
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/configurations/FacilityAttrsConfig.java
@@ -0,0 +1,128 @@
+package cz.muni.ics.oidc.server.configurations;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.PostConstruct;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Configuration of Facility attributes
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class FacilityAttrsConfig {
+
+	private final static Logger log = LoggerFactory.getLogger(FacilityAttrsConfig.class);
+
+	private String checkGroupMembershipAttr;
+	private String registrationURLAttr;
+	private String allowRegistrationAttr;
+	private String dynamicRegistrationAttr;
+	private String voShortNamesAttr;
+	private String wayfFilterAttr;
+	private String wayfEFilterAttr;
+	private String testSpAttr;
+	private final Set<String> membershipAttrNames = new HashSet<>();
+	private final Set<String> filterAttrNames = new HashSet<>();
+
+	public String getCheckGroupMembershipAttr() {
+		return checkGroupMembershipAttr;
+	}
+
+	public void setCheckGroupMembershipAttr(String checkGroupMembershipAttr) {
+		membershipAttrNames.remove(this.checkGroupMembershipAttr);
+		membershipAttrNames.add(checkGroupMembershipAttr);
+		this.checkGroupMembershipAttr = checkGroupMembershipAttr;
+	}
+
+	public String getRegistrationURLAttr() {
+		return registrationURLAttr;
+	}
+
+	public void setRegistrationURLAttr(String registrationURLAttr) {
+		membershipAttrNames.remove(this.registrationURLAttr);
+		membershipAttrNames.add(registrationURLAttr);
+		this.registrationURLAttr = registrationURLAttr;
+	}
+
+	public String getAllowRegistrationAttr() {
+		return allowRegistrationAttr;
+	}
+
+	public void setAllowRegistrationAttr(String allowRegistrationAttr) {
+		membershipAttrNames.remove(this.allowRegistrationAttr);
+		membershipAttrNames.add(allowRegistrationAttr);
+		this.allowRegistrationAttr = allowRegistrationAttr;
+	}
+
+	public String getDynamicRegistrationAttr() {
+		return dynamicRegistrationAttr;
+	}
+
+	public void setDynamicRegistrationAttr(String dynamicRegistrationAttr) {
+		membershipAttrNames.remove(this.dynamicRegistrationAttr);
+		membershipAttrNames.add(dynamicRegistrationAttr);
+		this.dynamicRegistrationAttr = dynamicRegistrationAttr;
+	}
+
+	public String getVoShortNamesAttr() {
+		return voShortNamesAttr;
+	}
+
+	public void setVoShortNamesAttr(String voShortNamesAttr) {
+		membershipAttrNames.remove(this.voShortNamesAttr);
+		membershipAttrNames.add(voShortNamesAttr);
+		this.voShortNamesAttr = voShortNamesAttr;
+	}
+
+	public String getWayfFilterAttr() {
+		return wayfFilterAttr;
+	}
+
+	public void setWayfFilterAttr(String wayfFilterAttr) {
+		filterAttrNames.remove(this.wayfFilterAttr);
+		filterAttrNames.add(wayfFilterAttr);
+		this.wayfFilterAttr = wayfFilterAttr;
+	}
+
+	public String getWayfEFilterAttr() {
+		return wayfEFilterAttr;
+	}
+
+	public void setWayfEFilterAttr(String wayfEFilterAttr) {
+		filterAttrNames.remove(this.wayfEFilterAttr);
+		filterAttrNames.add(wayfEFilterAttr);
+		this.wayfEFilterAttr = wayfEFilterAttr;
+	}
+
+	public Set<String> getMembershipAttrNames() {
+		return membershipAttrNames;
+	}
+
+	public Set<String> getFilterAttrNames() {
+		return filterAttrNames;
+	}
+
+	public String getTestSpAttr() {
+		return testSpAttr;
+	}
+
+	public void setTestSpAttr(String testSpAttr) {
+		this.testSpAttr = testSpAttr;
+	}
+
+	@PostConstruct
+	public void postInit() {
+		log.info("Facility attributes initialized");
+		log.info("Check group membership attr mapped to urn: {}", checkGroupMembershipAttr);
+		log.info("Allow registration attr mapped to urn: {}", allowRegistrationAttr);
+		log.info("Registration URL attr mapped to urn: {}", registrationURLAttr);
+		log.info("Allow dynamic registration attr mapped to urn: {}", dynamicRegistrationAttr);
+		log.info("Vo short names attr mapped to urn: {}", voShortNamesAttr);
+		log.info("IDP Filter attr mapped to urn: {}", wayfFilterAttr);
+		log.info("IDP E-Filter attr mapped to urn: {}", wayfEFilterAttr);
+		log.info("Test SP attr mapped to urn: {}", testSpAttr);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/configurations/PerunOidcConfig.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/configurations/PerunOidcConfig.java
new file mode 100644
index 000000000..c65262e19
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/configurations/PerunOidcConfig.java
@@ -0,0 +1,275 @@
+package cz.muni.ics.oidc.server.configurations;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import javax.annotation.PostConstruct;
+import javax.servlet.ServletContext;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.web.util.UriComponentsBuilder;
+
+/**
+ * Configuration of OIDC server in context of Perun.
+ * Logs some interesting facts.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunOidcConfig {
+	private final static Logger log = LoggerFactory.getLogger(PerunOidcConfig.class);
+	private static final String OIDC_POM_FILE = "/META-INF/maven/cz.muni.ics/perun-oidc-server-webapp/pom.properties";
+
+	private ConfigurationPropertiesBean configBean;
+	private String rpcUrl;
+	private String jwk;
+	private String jdbcUrl;
+	private String theme;
+	private String baseURL;
+	private String registrarUrl;
+	private String samlLoginURL;
+	private String samlLogoutURL;
+	private String samlResourcesURL;
+	private boolean askPerunForIdpFiltersEnabled;
+	private String perunOIDCVersion;
+	private String proxyExtSourceName;
+	private Set<String> idTokenScopes;
+	private List<String> availableLangs;
+	private boolean fillMissingUserAttrs;
+	private boolean addClientIdToAcrs = false;
+
+	@Autowired
+	private ServletContext servletContext;
+
+	@Autowired
+	private Properties coreProperties;
+	private String localizationFilesPath;
+	private String webClassesFilePath;
+	private String emailContact;
+	private String rpcEnabled;
+
+	public void setRpcUrl(String rpcUrl) {
+		this.rpcUrl = rpcUrl;
+	}
+
+	public void setConfigBean(ConfigurationPropertiesBean configBean) {
+		this.configBean = configBean;
+	}
+
+	public void setJwk(String jwk) {
+		this.jwk = jwk;
+	}
+
+	public void setJdbcUrl(String jdbcUrl) {
+		this.jdbcUrl = jdbcUrl;
+	}
+
+	public void setTheme(String theme) {
+		this.theme = theme;
+	}
+
+	public String getTheme() {
+		return theme;
+	}
+
+	public String getBaseURL() {
+		return baseURL;
+	}
+
+	public void setBaseURL(String baseURL) {
+		this.baseURL = baseURL;
+	}
+
+	public String getSamlResourcesURL() {
+		return samlResourcesURL;
+	}
+
+	public void setSamlResourcesURL(String samlResourcesURL) {
+		this.samlResourcesURL = samlResourcesURL;
+	}
+
+	public String getRegistrarUrl() {
+		return registrarUrl;
+	}
+
+	public void setRegistrarUrl(String registrarUrl) {
+		this.registrarUrl = registrarUrl;
+	}
+
+	public void setIdTokenScopes(Set<String> idTokenScopes) {
+		this.idTokenScopes = idTokenScopes;
+	}
+
+	public Set<String> getIdTokenScopes() {
+		return idTokenScopes;
+	}
+
+	public String getPerunOIDCVersion() {
+		if (perunOIDCVersion == null) {
+			perunOIDCVersion = readPomVersion(OIDC_POM_FILE);
+		}
+		return perunOIDCVersion;
+	}
+
+	private String readPomVersion(String file) {
+		try {
+			Properties p = new Properties();
+			p.load(servletContext.getResourceAsStream(file));
+			return p.getProperty("version");
+		} catch (IOException e) {
+			log.error("cannot read file " + file, e);
+			return "UNKNOWN";
+		}
+	}
+
+	public void setSamlLoginURL(String samlLoginURL) {
+		this.samlLoginURL = samlLoginURL;
+	}
+
+	public String getSamlLoginURL() {
+		return samlLoginURL;
+	}
+
+	public void setSamlLogoutURL(String samlLogoutURL) {
+		this.samlLogoutURL = samlLogoutURL;
+	}
+
+	public String getSamlLogoutURL() {
+		return samlLogoutURL;
+	}
+
+	public ConfigurationPropertiesBean getConfigBean() {
+		return configBean;
+	}
+
+	public boolean isAskPerunForIdpFiltersEnabled() {
+		return askPerunForIdpFiltersEnabled;
+	}
+
+	public void setAskPerunForIdpFiltersEnabled(boolean askPerunForIdpFiltersEnabled) {
+		this.askPerunForIdpFiltersEnabled = askPerunForIdpFiltersEnabled;
+	}
+
+	public String getProxyExtSourceName() {
+		return proxyExtSourceName;
+	}
+
+	public void setProxyExtSourceName(String proxyExtSourceName) {
+		if (proxyExtSourceName == null || proxyExtSourceName.isEmpty()) {
+			this.proxyExtSourceName = null;
+		} else {
+			this.proxyExtSourceName = proxyExtSourceName;
+		}
+	}
+
+	public List<String> getAvailableLangs() {
+		return availableLangs;
+	}
+
+	public void setAvailableLangs(List<String> availableLangs) {
+		this.availableLangs = availableLangs;
+	}
+
+	public String getLocalizationFilesPath() {
+		return localizationFilesPath;
+	}
+
+	public void setLocalizationFilesPath(String localizationFilesPath) {
+		this.localizationFilesPath = localizationFilesPath;
+	}
+
+	public boolean isFillMissingUserAttrs() {
+		return fillMissingUserAttrs;
+	}
+
+	public void setFillMissingUserAttrs(boolean fillMissingUserAttrs) {
+		this.fillMissingUserAttrs = fillMissingUserAttrs;
+	}
+
+	public String getWebClassesFilePath() {
+		return webClassesFilePath;
+	}
+
+	public void setWebClassesFilePath(String webClassesFilePath) {
+		this.webClassesFilePath = webClassesFilePath;
+	}
+
+	public String getEmailContact() {
+		return emailContact;
+	}
+
+	public void setEmailContact(String emailContact) {
+		this.emailContact = emailContact;
+	}
+
+
+	public void setRpcEnabled(String rpcEnabled) {
+		this.rpcEnabled = rpcEnabled;
+	}
+
+	public String getRpcEnabled() {
+		return rpcEnabled;
+	}
+
+	public boolean isAddClientIdToAcrs() {
+		return addClientIdToAcrs;
+	}
+
+	public void setAddClientIdToAcrs(boolean addClientIdToAcrs) {
+		this.addClientIdToAcrs = addClientIdToAcrs;
+	}
+
+	@PostConstruct
+	public void postInit() {
+		//load URLs from properties if available or construct them from issuer URL
+		if (samlLoginURL != null && !samlLoginURL.trim().isEmpty()) {
+			samlLoginURL = samlLoginURL.trim();
+		} else {
+			samlLoginURL = UriComponentsBuilder.fromHttpUrl(configBean.getIssuer()).replacePath("/Shibboleth.sso/Login").build().toString();
+		}
+
+		if (samlLogoutURL != null && !samlLogoutURL.trim().isEmpty()) {
+			samlLogoutURL = samlLogoutURL.trim();
+		} else {
+			samlLogoutURL = UriComponentsBuilder.fromHttpUrl(configBean.getIssuer()).replacePath("/Shibboleth.sso/Logout").build().toString();
+		}
+
+		if (samlResourcesURL != null && !samlResourcesURL.trim().isEmpty()) {
+			samlResourcesURL = samlResourcesURL.trim();
+		} else {
+			samlResourcesURL = UriComponentsBuilder.fromHttpUrl(configBean.getIssuer()).replacePath("/proxy").build().toString();
+		}
+	}
+
+	//called when all beans are initialized, but twice, once for root context and once for spring-servlet
+	@EventListener
+	public void handleContextRefresh(ContextRefreshedEvent event) {
+		if (event.getApplicationContext().getParent() == null) {
+			//log info
+			log.info("Perun OIDC initialized");
+			log.info("Mitreid config URL: {}", configBean.getIssuer());
+			log.info("RPC URL: {}", rpcUrl);
+			log.info("JSON Web Keys: {}", jwk);
+			log.info("JDBC URL: {}", jdbcUrl);
+			log.info("LDAP: ldaps://{}/{}", coreProperties.getProperty("ldap.host"), coreProperties.getProperty("ldap.baseDN"));
+			log.info("FILL MISSING USER ATTRS: {}", fillMissingUserAttrs);
+			log.info("THEME: {}", theme);
+			log.info("baseURL: {}", baseURL);
+			log.info("LOGIN  URL: {}", samlLoginURL);
+			log.info("LOGOUT URL: {}", samlLogoutURL);
+			log.info("samlResourcesURL: {}", samlResourcesURL);
+			log.info("Registrar URL: {}", registrarUrl);
+			log.info("accessTokenClaimsModifier: {}", coreProperties.getProperty("accessTokenClaimsModifier"));
+			log.info("Proxy EXT_SOURCE name: {}", proxyExtSourceName);
+			log.info("Available languages: {}", availableLangs);
+			log.info("Localization files path: {}", localizationFilesPath);
+			log.info("Email contact: {}", emailContact);
+			log.info("Perun OIDC version: {}", getPerunOIDCVersion());
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/Affiliation.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/Affiliation.java
new file mode 100644
index 000000000..1544e1202
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/Affiliation.java
@@ -0,0 +1,41 @@
+package cz.muni.ics.oidc.server.connectors;
+
+/**
+ * Model representing affilitation of user.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class Affiliation {
+
+	private final String source;
+	private final String value;
+	private final long asserted;
+
+	public Affiliation(String source, String value, long asserted) {
+		this.source = source;
+		this.value = value;
+		this.asserted = asserted;
+	}
+
+	public String getSource() {
+		return source;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public long getAsserted() {
+		return asserted;
+	}
+
+	@Override
+	public String toString() {
+		return "Affiliation{" +
+				"source='" + source + '\'' +
+				", value='" + value + '\'' +
+				", asserted=" + asserted +
+				'}';
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/PerunConnectorLdap.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/PerunConnectorLdap.java
new file mode 100644
index 000000000..edc3d3d06
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/PerunConnectorLdap.java
@@ -0,0 +1,147 @@
+package cz.muni.ics.oidc.server.connectors;
+
+import com.google.common.base.Strings;
+import cz.muni.ics.oidc.aop.LogTimes;
+import java.util.List;
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.apache.directory.api.ldap.model.message.SearchScope;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory;
+import org.apache.directory.ldap.client.api.DefaultPoolableLdapConnectionFactory;
+import org.apache.directory.ldap.client.api.LdapConnectionConfig;
+import org.apache.directory.ldap.client.api.LdapConnectionPool;
+import org.apache.directory.ldap.client.api.NoVerificationTrustManager;
+import org.apache.directory.ldap.client.api.search.FilterBuilder;
+import org.apache.directory.ldap.client.template.EntryMapper;
+import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+
+/**
+ * Connector for calling Perun LDAP
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunConnectorLdap implements DisposableBean {
+
+	private static final Logger log = LoggerFactory.getLogger(PerunConnectorLdap.class);
+
+	private final String baseDN;
+	private final LdapConnectionPool pool;
+	private final LdapConnectionTemplate ldap;
+
+	public PerunConnectorLdap(String ldapHost, String ldapUser, String ldapPassword, int port, boolean useTLS,
+							  boolean useSSL, boolean allowUntrustedSsl, long timeoutSecs, String baseDN) {
+		if (ldapHost == null || ldapHost.trim().isEmpty()) {
+			throw new IllegalArgumentException("Host cannot be null or empty");
+		} else if (baseDN == null || baseDN.trim().isEmpty()) {
+			throw new IllegalArgumentException("baseDN cannot be null or empty");
+		}
+
+		this.baseDN = baseDN;
+		LdapConnectionConfig config = getConfig(ldapHost, port, useTLS, useSSL, allowUntrustedSsl);
+		if (ldapUser != null && !ldapUser.isEmpty()) {
+			log.debug("setting ldap user to {}", ldapUser);
+			config.setName(ldapUser);
+		}
+		if (ldapPassword != null && !ldapPassword.isEmpty()) {
+			log.debug("setting ldap password");
+			config.setCredentials(ldapPassword);
+		}
+		DefaultLdapConnectionFactory factory = new DefaultLdapConnectionFactory(config);
+		factory.setTimeOut(timeoutSecs * 1000L);
+
+		GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
+		poolConfig.setTestOnBorrow(true);
+
+		pool = new LdapConnectionPool(new DefaultPoolableLdapConnectionFactory(factory), poolConfig);
+		ldap = new LdapConnectionTemplate(pool);
+		log.debug("initialized LDAP connector");
+	}
+
+	public String getBaseDN() {
+		return baseDN;
+	}
+
+	private LdapConnectionConfig getConfig(String host, int port, boolean useTLS, boolean useSSL,
+										   boolean allowUntrustedSsl) {
+		LdapConnectionConfig config = new LdapConnectionConfig();
+		config.setLdapHost(host);
+		config.setLdapPort(port);
+		config.setUseSsl(useSSL);
+		config.setUseTls(useTLS);
+		if (allowUntrustedSsl) {
+			config.setTrustManagers(new NoVerificationTrustManager());
+		}
+
+		return config;
+	}
+
+	@Override
+	public void destroy() {
+		if (!pool.isClosed()) {
+			pool.close();
+		}
+	}
+
+	/**
+	 * Search for the first entry that satisfies criteria.
+	 * @param dnPrefix Prefix to be added to the base DN. (i.e. ou=People) !DO NOT END WITH A COMMA!
+	 * @param filter Filter for entries
+	 * @param scope Search scope
+	 * @param attributes Attributes to be fetch for entry
+	 * @param entryMapper Mapper of entries to the target class T
+	 * @param <T> Class that the result should be mapped to.
+	 * @return Found entry mapped to target class
+	 */
+	@LogTimes
+	public <T> T searchFirst(String dnPrefix, FilterBuilder filter, SearchScope scope, String[] attributes,
+							 EntryMapper<T> entryMapper)
+	{
+		Dn fullDn = getFullDn(dnPrefix);
+		return ldap.searchFirst(fullDn, filter, scope, attributes, entryMapper);
+	}
+
+	/**
+	 * Perform lookup for the entry that satisfies criteria.
+	 * @param dnPrefix Prefix to be added to the base DN. (i.e. ou=People) !DO NOT END WITH A COMMA!
+	 * @param attributes Attributes to be fetch for entry
+	 * @param entryMapper Mapper of entries to the target class T
+	 * @param <T> Class that the result should be mapped to.
+	 * @return Found entry mapped to target class
+	 */
+	@LogTimes
+	public <T> T lookup(String dnPrefix, String[] attributes, EntryMapper<T> entryMapper) {
+		Dn fullDn = getFullDn(dnPrefix);
+		return ldap.lookup(fullDn, attributes, entryMapper);
+	}
+
+	/**
+	 * Search for the entries satisfy criteria.
+	 * @param dnPrefix Prefix to be added to the base DN. (i.e. ou=People) !DO NOT END WITH A COMMA!
+	 * @param filter Filter for entries
+	 * @param scope Search scope
+	 * @param attributes Attributes to be fetch for entry
+	 * @param entryMapper Mapper of entries to the target class T
+	 * @param <T> Class that the result should be mapped to.
+	 * @return List of found entries mapped to target class
+	 */
+	@LogTimes
+	public <T> List<T> search(String dnPrefix, FilterBuilder filter, SearchScope scope, String[] attributes,
+							  EntryMapper<T> entryMapper)
+	{
+		Dn fullDn = getFullDn(dnPrefix);
+		return ldap.search(fullDn, filter, scope, attributes, entryMapper);
+	}
+
+	private Dn getFullDn(String prefix) {
+		String dn = baseDN;
+		if (!Strings.isNullOrEmpty(prefix)) {
+			dn = prefix + "," + baseDN;
+		}
+
+		return ldap.newDn(dn);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/PerunConnectorRpc.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/PerunConnectorRpc.java
new file mode 100644
index 000000000..c41b05e4b
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/connectors/PerunConnectorRpc.java
@@ -0,0 +1,183 @@
+package cz.muni.ics.oidc.server.connectors;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import cz.muni.ics.oidc.aop.LogTimes;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.PostConstruct;
+import org.apache.http.HeaderElement;
+import org.apache.http.HeaderElementIterator;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.ConnectionKeepAliveStrategy;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.message.BasicHeaderElementIterator;
+import org.apache.http.protocol.HTTP;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.client.InterceptingClientHttpRequestFactory;
+import org.springframework.http.client.support.BasicAuthorizationInterceptor;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Connector for calling Perun RPC
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunConnectorRpc {
+
+	private static final Logger log = LoggerFactory.getLogger(PerunConnectorRpc.class);
+
+	public static final String ATTRIBUTES_MANAGER = "attributesManager";
+	public static final String FACILITIES_MANAGER = "facilitiesManager";
+	public static final String GROUPS_MANAGER = "groupsManager";
+	public static final String MEMBERS_MANAGER = "membersManager";
+	public static final String REGISTRAR_MANAGER = "registrarManager";
+	public static final String SEARCHER = "searcher";
+	public static final String USERS_MANAGER = "usersManager";
+	public static final String VOS_MANAGER = "vosManager";
+	public static final String RESOURCES_MANAGER = "resourcesManager";
+
+	private String perunUrl;
+	private String perunUser;
+	private String perunPassword;
+	private boolean isEnabled;
+	private String serializer;
+	private RestTemplate restTemplate;
+
+	public PerunConnectorRpc(String perunUrl, String perunUser, String perunPassword, String enabled, String serializer) {
+		this.isEnabled = Boolean.parseBoolean(enabled);
+		this.setPerunUrl(perunUrl);
+		this.setPerunUser(perunUser);
+		this.setPerunPassword(perunPassword);
+		this.setSerializer(serializer);
+	}
+
+	public void setEnabled(String enabled) {
+		this.isEnabled = Boolean.parseBoolean(enabled);
+	}
+
+	public boolean isEnabled() {
+		return isEnabled;
+	}
+
+	public void setPerunUrl(String perunUrl) {
+		if (!StringUtils.hasText(perunUrl)) {
+			throw new IllegalArgumentException("Perun URL cannot be null or empty");
+		} else if (perunUrl.endsWith("/")) {
+			perunUrl = perunUrl.substring(0, perunUrl.length() - 1);
+		}
+
+		this.perunUrl = perunUrl;
+	}
+
+	public void setPerunUser(String perunUser) {
+		if (!StringUtils.hasText(perunUser)) {
+			throw new IllegalArgumentException("Perun USER cannot be null or empty");
+		}
+
+		this.perunUser = perunUser;
+	}
+
+	public void setPerunPassword(String perunPassword) {
+		if (!StringUtils.hasText(perunPassword)) {
+			throw new IllegalArgumentException("Perun PASSWORD cannot be null or empty");
+		}
+
+		this.perunPassword = perunPassword;
+	}
+
+	public void setSerializer(String serializer) {
+		if (!StringUtils.hasText(serializer)) {
+			this.serializer = "json";
+		}
+
+		this.serializer = serializer;
+	}
+
+	@PostConstruct
+	public void postInit() {
+		restTemplate = new RestTemplate();
+		//HTTP connection pooling, see https://howtodoinjava.com/spring-restful/resttemplate-httpclient-java-config/
+		RequestConfig requestConfig = RequestConfig.custom()
+				.setConnectionRequestTimeout(30000) // The timeout when requesting a connection from the connection manager
+				.setConnectTimeout(30000) // Determines the timeout in milliseconds until a connection is established
+				.setSocketTimeout(60000) // The timeout for waiting for data
+				.build();
+		PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
+		poolingConnectionManager.setMaxTotal(20); // maximum connections total
+		poolingConnectionManager.setDefaultMaxPerRoute(18);
+		ConnectionKeepAliveStrategy connectionKeepAliveStrategy = (response, context) -> {
+			HeaderElementIterator it = new BasicHeaderElementIterator
+					(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
+			while (it.hasNext()) {
+				HeaderElement he = it.nextElement();
+				String param = he.getName();
+				String value = he.getValue();
+
+				if (value != null && param.equalsIgnoreCase("timeout")) {
+					return Long.parseLong(value) * 1000;
+				}
+			}
+			return 20000L;
+		};
+		CloseableHttpClient httpClient = HttpClients.custom()
+				.setDefaultRequestConfig(requestConfig)
+				.setConnectionManager(poolingConnectionManager)
+				.setKeepAliveStrategy(connectionKeepAliveStrategy)
+				.build();
+		HttpComponentsClientHttpRequestFactory poolingRequestFactory = new HttpComponentsClientHttpRequestFactory();
+		poolingRequestFactory.setHttpClient(httpClient);
+		//basic authentication
+		List<ClientHttpRequestInterceptor> interceptors =
+				Collections.singletonList(new BasicAuthorizationInterceptor(perunUser, perunPassword));
+		InterceptingClientHttpRequestFactory authenticatingRequestFactory = new InterceptingClientHttpRequestFactory(poolingRequestFactory, interceptors);
+		restTemplate.setRequestFactory(authenticatingRequestFactory);
+	}
+
+	/**
+	 * Make post call to Perun RPC
+	 * @param manager String value representing manager to be called. Use constants from this class.
+	 * @param method Method to be called (i.e. getUserById)
+	 * @param map Map of parameters to be passed as request body
+	 * @return Response from Perun
+	 */
+	@LogTimes
+	public JsonNode post(String manager, String method, Map<String, Object> map) {
+		if (!this.isEnabled) {
+			return JsonNodeFactory.instance.nullNode();
+		}
+
+		String actionUrl = perunUrl + '/' + serializer + '/' + manager + '/' + method;
+		//make the call
+		try {
+			log.debug("calling {} with {}", actionUrl, map);
+			return restTemplate.postForObject(actionUrl, map, JsonNode.class);
+		} catch (HttpClientErrorException ex) {
+			MediaType contentType = ex.getResponseHeaders().getContentType();
+			String body = ex.getResponseBodyAsString();
+			log.error("HTTP ERROR " + ex.getRawStatusCode() + " URL " + actionUrl + " Content-Type: " + contentType);
+			if ("json".equals(contentType.getSubtype())) {
+				try {
+					log.error(new ObjectMapper().readValue(body, JsonNode.class).path("message").asText());
+				} catch (IOException e) {
+					log.error("cannot parse error message from JSON", e);
+				}
+			} else {
+				log.error(ex.getMessage());
+			}
+			throw new RuntimeException("cannot connect to Perun RPC", ex);
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/AddHeaderInterceptor.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/AddHeaderInterceptor.java
new file mode 100644
index 000000000..70576bc7b
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/AddHeaderInterceptor.java
@@ -0,0 +1,30 @@
+package cz.muni.ics.oidc.server.elixir;
+
+import java.io.IOException;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+
+/**
+ * HTTP Request Interceptor which adds a specific header to the request.
+ * Name of the header and value are passed in constructor.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+class AddHeaderInterceptor implements ClientHttpRequestInterceptor {
+
+	private final String header;
+	private final String value;
+
+	AddHeaderInterceptor(String header, String value) {
+		this.header = header;
+		this.value = value;
+	}
+
+	@Override
+	public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
+		request.getHeaders().add(header, value);
+		return execution.execute(request, body);
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/ElixirAccessTokenModifier.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/ElixirAccessTokenModifier.java
new file mode 100644
index 000000000..b4709d903
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/ElixirAccessTokenModifier.java
@@ -0,0 +1,36 @@
+package cz.muni.ics.oidc.server.elixir;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import cz.muni.ics.oidc.server.PerunAccessTokenEnhancer;
+import java.util.Collections;
+import java.util.Set;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+
+/**
+ * Implements changes required by GA4GH specification followed by the ELIXIR AAI.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+@SuppressWarnings("unused")
+public class ElixirAccessTokenModifier implements PerunAccessTokenEnhancer.AccessTokenClaimsModifier {
+
+	private final static Logger log = LoggerFactory.getLogger(ElixirAccessTokenModifier.class);
+
+	public ElixirAccessTokenModifier() {
+	}
+
+	@Override
+	public void modifyClaims(String sub, JWTClaimsSet.Builder builder, OAuth2AccessToken accessToken, OAuth2Authentication authentication, UserInfo userInfo) {
+		Set<String> scopes = accessToken.getScope();
+		//GA4GH
+		if (scopes.contains(GA4GHClaimSource.GA4GH_SCOPE)) {
+			log.debug("adding claims required by GA4GH to access token");
+			builder.audience(Collections.singletonList(authentication.getOAuth2Request().getClientId()));
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/GA4GHClaimSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/GA4GHClaimSource.java
new file mode 100644
index 000000000..37e6935c2
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/GA4GHClaimSource.java
@@ -0,0 +1,593 @@
+package cz.muni.ics.oidc.server.elixir;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKMatcher;
+import com.nimbusds.jose.jwk.JWKSelector;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.RemoteJWKSet;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.jwt.SignedJWT;
+import cz.muni.ics.oidc.models.PerunAttribute;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import cz.muni.ics.oidc.server.connectors.Affiliation;
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.openid.connect.web.JWKSetPublishingEndpoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.InterceptingClientHttpRequestFactory;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Class producing GA4GH Passport claim. The claim is specified in
+ * https://bit.ly/ga4gh-passport-v1
+ *
+ * Configuration (replace [claimName] with the name of the claim):
+ * <ul>
+ *     <li><b>custom.claim.[claimName].source.config_file</b> - full path to the configuration file for this claim. See
+ *     configuration templates for such a file.</li>
+ *     <li><b>custom.claim.[claimName].source.bonaFideStatus.attr</b> - mapping for bonaFideStatus Attriute</li>
+ *     <li><b>custom.claim.[claimName].source.bonaFideStatusREMS.attr</b> - mapping for bonaFideStatus Attriute</li>
+ *     <li><b>custom.claim.[claimName].source.groupAffiliations.attr</b> - mapping for groupAffiliations Attriute</li>
+ * </ul>
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class GA4GHClaimSource extends ClaimSource {
+
+	static final String GA4GH_SCOPE = "ga4gh_passport_v1";
+	private static final String GA4GH_CLAIM = "ga4gh_passport_v1";
+
+	private static final Logger log = LoggerFactory.getLogger(GA4GHClaimSource.class);
+
+	private static final String BONA_FIDE_URL = "https://doi.org/10.1038/s41431-018-0219-y";
+	private static final String ELIXIR_ORG_URL = "https://elixir-europe.org/";
+	private static final String ELIXIR_ID = "elixir_id";
+
+	private final JWTSigningAndValidationService jwtService;
+	private final URI jku;
+	private final String issuer;
+	private static final List<ClaimRepository> claimRepositories = new ArrayList<>();
+	private static final Map<URI, RemoteJWKSet<SecurityContext>> remoteJwkSets = new HashMap<>();
+	private static final Map<URI, String> signers = new HashMap<>();
+
+	private final String bonaFideStatusAttr;
+	private final String bonaFideStatusREMSAttr;
+	private final String groupAffiliationsAttr;
+
+	public GA4GHClaimSource(ClaimSourceInitContext ctx) throws URISyntaxException {
+		super(ctx);
+		log.debug("initializing");
+		//remember context
+		jwtService = ctx.getJwtService();
+		issuer = ctx.getPerunOidcConfig().getConfigBean().getIssuer();
+		jku = new URI(issuer + JWKSetPublishingEndpoint.URL);
+		// load config file
+		parseConfigFile(ctx.getProperty("config_file", "/etc/mitreid/elixir/ga4gh_config.yml"));
+		bonaFideStatusAttr = ctx.getProperty("bonaFideStatus.attr", null);
+		bonaFideStatusREMSAttr = ctx.getProperty("bonaFideStatusREMS.attr", null);
+		groupAffiliationsAttr = ctx.getProperty("groupAffiliations.attr", null);
+	}
+
+	static void parseConfigFile(String file) {
+		YAMLMapper mapper = new YAMLMapper();
+		try {
+			JsonNode root = mapper.readValue(new File(file), JsonNode.class);
+			// prepare claim repositories
+			for (JsonNode repo : root.path("repos")) {
+				String name = repo.path("name").asText();
+				String actionURL = repo.path("url").asText();
+				JsonNode headers = repo.path("headers");
+				Map<String, String> headersWithValues = new HashMap<>();
+				for (JsonNode header: headers) {
+					headersWithValues.put(header.path("header").asText(), header.path("value").asText());
+				}
+				if (actionURL == null || headersWithValues.isEmpty()) {
+					log.error("claim repository " + repo + " not defined with url|auth_header|auth_value ");
+					continue;
+				}
+				RestTemplate restTemplate = new RestTemplate();
+				restTemplate.setRequestFactory(
+						new InterceptingClientHttpRequestFactory(restTemplate.getRequestFactory(),
+								headersWithValues.entrySet()
+										.stream()
+										.map(e -> new AddHeaderInterceptor(e.getKey(), e.getValue()))
+										.collect(Collectors.toList()))
+				);
+				claimRepositories.add(new ClaimRepository(name, restTemplate, actionURL));
+				log.info("GA4GH Claims Repository " + name + " configured at " + actionURL);
+			}
+			// prepare claim signers
+			for (JsonNode signer : root.path("signers")) {
+				String name = signer.path("name").asText();
+				String jwks = signer.path("jwks").asText();
+				try {
+					URL jku = new URL(jwks);
+					remoteJwkSets.put(jku.toURI(), new RemoteJWKSet<>(jku));
+					signers.put(jku.toURI(), name);
+					log.info("JWKS Signer " + name + " added with keys " + jwks);
+				} catch (MalformedURLException | URISyntaxException e) {
+					log.error("cannot add to RemoteJWKSet map: " + name + " " + jwks, e);
+				}
+			}
+		} catch (IOException ex) {
+			log.error("cannot read GA4GH config file", ex);
+		}
+	}
+
+	@Override
+	public Set<String> getAttrIdentifiers() {
+		Set<String> set = new HashSet<>();
+		if (bonaFideStatusAttr != null) {
+			set.add(bonaFideStatusAttr);
+		}
+		if (bonaFideStatusREMSAttr != null) {
+			set.add(bonaFideStatusREMSAttr);
+		}
+		if (groupAffiliationsAttr != null) {
+			set.add(groupAffiliationsAttr);
+		}
+		return set;
+	}
+
+	@Override
+	public JsonNode produceValue(ClaimSourceProduceContext pctx) {
+		if (pctx.getClient() == null) {
+			log.debug("client is not set");
+			return JsonNodeFactory.instance.textNode("Global Alliance For Genomic Health structured claim");
+		}
+		if (!pctx.getClient().getScope().contains(GA4GH_SCOPE)) {
+			log.debug("Client '{}' does not have scope ga4gh", pctx.getClient().getClientName());
+			return null;
+		}
+
+		List<Affiliation> affiliations = pctx.getPerunAdapter()
+				.getAdapterRpc()
+				.getUserExtSourcesAffiliations(pctx.getPerunUserId());
+
+		ArrayNode ga4gh_passport_v1 = JsonNodeFactory.instance.arrayNode();
+		long now = Instant.now().getEpochSecond();
+		addAffiliationAndRoles(now, pctx, ga4gh_passport_v1, affiliations);
+		addAcceptedTermsAndPolicies(now, pctx, ga4gh_passport_v1);
+		addResearcherStatuses(now, pctx, ga4gh_passport_v1, affiliations);
+		addControlledAccessGrants(now, pctx, ga4gh_passport_v1);
+		return ga4gh_passport_v1;
+	}
+
+
+	private void addAffiliationAndRoles(long now, ClaimSourceProduceContext pctx, ArrayNode passport, List<Affiliation> affiliations) {
+		//by=system for users with affiliation asserted by their IdP (set in UserExtSource attribute "affiliation")
+		for (Affiliation affiliation : affiliations) {
+			//expires 1 year after the last login from the IdP asserting the affiliation
+			long expires = Instant.ofEpochSecond(affiliation.getAsserted()).atZone(ZoneId.systemDefault()).plusYears(1L).toEpochSecond();
+			if (expires < now) continue;
+			JsonNode visa = createPassportVisa("AffiliationAndRole", pctx, affiliation.getValue(), affiliation.getSource(), "system", affiliation.getAsserted(), expires, null);
+			if (visa != null) {
+				passport.add(visa);
+			}
+		}
+	}
+
+	private void addAcceptedTermsAndPolicies(long now, ClaimSourceProduceContext pctx, ArrayNode passport) {
+		//by=self for members of the group 10432 "Bona Fide Researchers"
+		boolean userInGroup = pctx.getPerunAdapter().isUserInGroup(pctx.getPerunUserId(), 10432L);
+		if (userInGroup) {
+			PerunAttribute bonaFideStatus = pctx.getPerunAdapter()
+					.getAdapterRpc()
+					.getUserAttribute(pctx.getPerunUserId(), bonaFideStatusAttr);
+			String valueCreatedAt = bonaFideStatus.getValueCreatedAt();
+			long asserted;
+			if (valueCreatedAt != null) {
+				asserted = Timestamp.valueOf(valueCreatedAt).getTime() / 1000L;
+			} else {
+				asserted = System.currentTimeMillis() / 1000L;
+			}
+			long expires = Instant.ofEpochSecond(asserted).atZone(ZoneId.systemDefault()).plusYears(100L).toEpochSecond();
+			if (expires < now) return;
+			JsonNode visa = createPassportVisa("AcceptedTermsAndPolicies", pctx, BONA_FIDE_URL, ELIXIR_ORG_URL, "self", asserted, expires, null);
+			if (visa != null) {
+				passport.add(visa);
+			}
+		}
+	}
+
+	private void addResearcherStatuses(long now, ClaimSourceProduceContext pctx, ArrayNode passport, List<Affiliation> affiliations) {
+		//by=peer for users with attribute elixirBonaFideStatusREMS
+		PerunAttribute elixirBonaFideStatusREMS = pctx.getPerunAdapter()
+				.getAdapterRpc()
+				.getUserAttribute(pctx.getPerunUserId(), bonaFideStatusREMSAttr);
+
+		String valueCreatedAt = null;
+		if (elixirBonaFideStatusREMS != null) {
+			valueCreatedAt = elixirBonaFideStatusREMS.getValueCreatedAt();
+		}
+
+		if (valueCreatedAt != null) {
+			long asserted = Timestamp.valueOf(valueCreatedAt).getTime() / 1000L;
+			long expires = ZonedDateTime.now().plusYears(1L).toEpochSecond();
+			if (expires > now) {
+				JsonNode visa = createPassportVisa("ResearcherStatus", pctx, BONA_FIDE_URL, ELIXIR_ORG_URL, "peer", asserted, expires, null);
+				if (visa != null) {
+					passport.add(visa);
+				}
+			}
+		}
+		//by=system for users with faculty affiliation asserted by their IdP (set in UserExtSource attribute "affiliation")
+		for (Affiliation affiliation : affiliations) {
+			if (affiliation.getValue().startsWith("faculty@")) {
+				long expires = Instant.ofEpochSecond(affiliation.getAsserted()).atZone(ZoneId.systemDefault()).plusYears(1L).toEpochSecond();
+				if (expires < now) continue;
+				JsonNode visa = createPassportVisa("ResearcherStatus", pctx, BONA_FIDE_URL, affiliation.getSource(), "system", affiliation.getAsserted(), expires, null);
+				if (visa != null) {
+					passport.add(visa);
+				}
+			}
+		}
+		//by=so for users with faculty affiliation asserted by membership in a group with groupAffiliations attribute
+		for (Affiliation affiliation : pctx.getPerunAdapter().getGroupAffiliations(pctx.getPerunUserId(), groupAffiliationsAttr)) {
+			if (affiliation.getValue().startsWith("faculty@")) {
+				long expires = ZonedDateTime.now().plusYears(1L).toEpochSecond();
+				JsonNode visa = createPassportVisa("ResearcherStatus", pctx, BONA_FIDE_URL, ELIXIR_ORG_URL, "so", affiliation.getAsserted(), expires, null);
+				if (visa != null) {
+					passport.add(visa);
+				}
+			}
+		}
+	}
+
+	private static String isoDate(long linuxTime) {
+		return DateTimeFormatter.ISO_LOCAL_DATE.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(linuxTime), ZoneId.systemDefault()));
+	}
+
+	private static String isoDateTime(long linuxTime) {
+		return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(linuxTime), ZoneId.systemDefault()));
+	}
+
+	private JsonNode createPassportVisa(String type, ClaimSourceProduceContext pctx, String value, String source, String by, long asserted, long expires, JsonNode condition) {
+		long now = System.currentTimeMillis() / 1000L;
+		if (asserted > now) {
+			log.warn("visa asserted in future ! perunUserId {} sub {} type {} value {} source {} by {} asserted {}", pctx.getPerunUserId(), pctx.getSub(), type, value, source, by, Instant.ofEpochSecond(asserted));
+			return null;
+		}
+		if (expires <= now) {
+			log.warn("visa already expired ! perunUserId {} sub {} type {} value {} source {} by {} expired {}", pctx.getPerunUserId(), pctx.getSub(), type, value, source, by, Instant.ofEpochSecond(expires));
+			return null;
+		}
+
+		Map<String, Object> passportVisaObject = new HashMap<>();
+		passportVisaObject.put("type", type);
+		passportVisaObject.put("asserted", asserted);
+		passportVisaObject.put("value", value);
+		passportVisaObject.put("source", source);
+		passportVisaObject.put("by", by);
+		if (condition != null && !condition.isNull() && !condition.isMissingNode()) {
+			passportVisaObject.put("condition", condition);
+		}
+		JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.parse(jwtService.getDefaultSigningAlgorithm().getName()))
+				.keyID(jwtService.getDefaultSignerKeyId())
+				.type(JOSEObjectType.JWT)
+				.jwkURL(jku)
+				.build();
+		JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder()
+				.issuer(issuer)
+				.issueTime(new Date())
+				.expirationTime(new Date(expires * 1000L))
+				.subject(pctx.getSub())
+				.jwtID(UUID.randomUUID().toString())
+				.claim("ga4gh_visa_v1", passportVisaObject)
+				.build();
+		SignedJWT myToken = new SignedJWT(jwsHeader, jwtClaimsSet);
+		jwtService.signJwt(myToken);
+		return JsonNodeFactory.instance.textNode(myToken.serialize());
+	}
+
+	private void addControlledAccessGrants(long now, ClaimSourceProduceContext pctx, ArrayNode passport) {
+		Set<String> linkedIdentities = new HashSet<>();
+		//call Resource Entitlement Management System
+		for (ClaimRepository repo : claimRepositories) {
+			callPermissionsJwtAPI(repo, Collections.singletonMap(ELIXIR_ID, pctx.getSub()), pctx, passport, linkedIdentities);
+		}
+		if (!linkedIdentities.isEmpty()) {
+			for (String linkedIdentity : linkedIdentities) {
+				JsonNode visa = createPassportVisa("LinkedIdentities", pctx, linkedIdentity, ELIXIR_ORG_URL, "system", now, now + 3600L * 24 * 365, null);
+				if (visa != null) {
+					passport.add(visa);
+				}
+			}
+		}
+	}
+
+	private void callPermissionsJwtAPI(ClaimRepository repo, Map<String, String> uriVariables, ClaimSourceProduceContext pctx, ArrayNode passport, Set<String> linkedIdentities) {
+		JsonNode response = callHttpJsonAPI(repo, uriVariables);
+		if (response != null) {
+			JsonNode visas = response.path(GA4GH_CLAIM);
+			if (visas.isArray()) {
+				for (JsonNode visaNode : visas) {
+					if (visaNode.isTextual()) {
+						PassportVisa visa = parseAndVerifyVisa(visaNode.asText());
+						if (visa.isVerified()) {
+							log.debug("adding a visa to passport: {}", visa);
+							passport.add(passport.textNode(visa.getJwt()));
+							linkedIdentities.add(visa.getLinkedIdentity());
+						} else {
+							log.warn("skipping visa: {}", visa);
+						}
+					} else {
+						log.warn("element of ga4gh_passport_v1 is not a String: {}", visaNode);
+					}
+				}
+			} else {
+				log.warn("ga4gh_passport_v1 is not an array in {}", response);
+			}
+		}
+	}
+
+
+	public static PassportVisa parseAndVerifyVisa(String jwtString) {
+		PassportVisa visa = new PassportVisa(jwtString);
+		try {
+			SignedJWT signedJWT = (SignedJWT) JWTParser.parse(jwtString);
+			URI jku = signedJWT.getHeader().getJWKURL();
+			if (jku == null) {
+				log.error("JKU is missing in JWT header");
+				return visa;
+			}
+			visa.setSigner(signers.get(jku));
+			RemoteJWKSet<SecurityContext> remoteJWKSet = remoteJwkSets.get(jku);
+			if (remoteJWKSet == null) {
+				log.error("JKU {} is not among trusted key sets", jku);
+				return visa;
+			}
+			List<JWK> keys = remoteJWKSet.get(new JWKSelector(new JWKMatcher.Builder().keyID(signedJWT.getHeader().getKeyID()).build()), null);
+			RSASSAVerifier verifier = new RSASSAVerifier(((RSAKey) keys.get(0)).toRSAPublicKey());
+			visa.setVerified(signedJWT.verify(verifier));
+			if (visa.isVerified()) {
+				processPayload(visa, signedJWT.getPayload());
+			}
+		} catch (Exception ex) {
+			log.error("visa " + jwtString + " cannot be parsed and verified", ex);
+		}
+		return visa;
+	}
+
+	static private final ObjectMapper JSON_MAPPER = new ObjectMapper();
+
+	static private void processPayload(PassportVisa visa, Payload payload) throws IOException {
+		JsonNode doc = JSON_MAPPER.readValue(payload.toString(), JsonNode.class);
+		checkVisaKey(visa, doc, "sub");
+		checkVisaKey(visa, doc, "exp");
+		checkVisaKey(visa, doc, "iss");
+		JsonNode visa_v1 = doc.path("ga4gh_visa_v1");
+		checkVisaKey(visa, visa_v1, "type");
+		checkVisaKey(visa, visa_v1, "asserted");
+		checkVisaKey(visa, visa_v1, "value");
+		checkVisaKey(visa, visa_v1, "source");
+		checkVisaKey(visa, visa_v1, "by");
+		if (!visa.isVerified()) return;
+		long exp = doc.get("exp").asLong();
+		if (exp < Instant.now().getEpochSecond()) {
+			log.warn("visa expired on " + isoDateTime(exp));
+			visa.setVerified(false);
+			return;
+		}
+		visa.setLinkedIdentity(URLEncoder.encode(doc.get("sub").asText(), "utf-8") + "," + URLEncoder.encode(doc.get("iss").asText(), "utf-8"));
+		visa.setPrettyPayload(
+				visa_v1.get("type").asText() + ":  \"" + visa_v1.get("value").asText() + "\" asserted " + isoDate(visa_v1.get("asserted").asLong())
+		);
+	}
+
+	static private void checkVisaKey(PassportVisa visa, JsonNode jsonNode, String key) {
+		if (jsonNode.path(key).isMissingNode()) {
+			log.warn(key + " is missing");
+			visa.setVerified(false);
+		} else {
+			switch (key) {
+				case "sub":
+					visa.setSub(jsonNode.path(key).asText());
+					break;
+				case "iss":
+					visa.setIss(jsonNode.path(key).asText());
+					break;
+				case "type":
+					visa.setType(jsonNode.path(key).asText());
+					break;
+				case "value":
+					visa.setValue(jsonNode.path(key).asText());
+					break;
+			}
+		}
+	}
+
+	@SuppressWarnings("Duplicates")
+	private static JsonNode callHttpJsonAPI(ClaimRepository repo, Map<String, String> uriVariables) {
+		//get permissions data
+		try {
+			JsonNode result;
+			//make the call
+			try {
+				if (log.isDebugEnabled()) {
+					log.debug("calling Permissions API at {}", repo.getRestTemplate().getUriTemplateHandler().expand(repo.getActionURL(), uriVariables));
+				}
+				result = repo.getRestTemplate().getForObject(repo.getActionURL(), JsonNode.class, uriVariables);
+			} catch (HttpClientErrorException ex) {
+				MediaType contentType = ex.getResponseHeaders().getContentType();
+				String body = ex.getResponseBodyAsString();
+				log.error("HTTP ERROR " + ex.getRawStatusCode() + " URL " + repo.getActionURL() + " Content-Type: " + contentType);
+				if (ex.getRawStatusCode() == 404) {
+					log.warn("Got status 404 from Permissions endpoint {}, ELIXIR AAI user is not linked to user at Permissions API", repo.getActionURL());
+					return null;
+				}
+				if ("json".equals(contentType.getSubtype())) {
+					try {
+						log.error(new ObjectMapper().readValue(body, JsonNode.class).path("message").asText());
+					} catch (IOException e) {
+						log.error("cannot parse error message from JSON", e);
+					}
+				} else {
+					log.error("cannot make REST call, exception: {} message: {}", ex.getClass().getName(), ex.getMessage());
+				}
+				return null;
+			}
+			log.debug("Permissions API response: {}", result);
+			return result;
+		} catch (Exception ex) {
+			log.error("Cannot get dataset permissions", ex);
+		}
+		return null;
+	}
+
+	public static class PassportVisa {
+		String jwt;
+		boolean verified = false;
+		String linkedIdentity;
+		String signer;
+		String prettyPayload;
+		private String sub;
+		private String iss;
+		private String type;
+		private String value;
+
+		PassportVisa(String jwt) {
+			this.jwt = jwt;
+		}
+
+		public String getJwt() {
+			return jwt;
+		}
+
+		public boolean isVerified() {
+			return verified;
+		}
+
+		void setVerified(boolean verified) {
+			this.verified = verified;
+		}
+
+		String getLinkedIdentity() {
+			return linkedIdentity;
+		}
+
+		void setLinkedIdentity(String linkedIdentity) {
+			this.linkedIdentity = linkedIdentity;
+		}
+
+		void setSigner(String signer) {
+			this.signer = signer;
+		}
+
+		void setPrettyPayload(String prettyPayload) {
+			this.prettyPayload = prettyPayload;
+		}
+
+		public String getPrettyString() {
+			return prettyPayload + ", signed by " + signer;
+		}
+
+		@Override
+		public String toString() {
+			return "PassportVisa{" +
+//					"jwt='" + jwt + '\'' +
+					"  type=" + type +
+					", sub=" + sub +
+					", iss=" + iss +
+					", value=" + value +
+					", verified=" + verified +
+					", linkedIdentity=" + linkedIdentity +
+					'}';
+		}
+
+		public void setSub(String sub) {
+			this.sub = sub;
+		}
+
+		public String getSub() {
+			return sub;
+		}
+
+		public void setIss(String iss) {
+			this.iss = iss;
+		}
+
+		public String getIss() {
+			return iss;
+		}
+
+		public void setType(String type) {
+			this.type = type;
+		}
+
+		public String getType() {
+			return type;
+		}
+
+		public void setValue(String value) {
+			this.value = value;
+		}
+
+		public String getValue() {
+			return value;
+		}
+	}
+
+	public static class ClaimRepository {
+		private String name;
+		private RestTemplate restTemplate;
+		private String actionURL;
+
+		public ClaimRepository(String name, RestTemplate restTemplate, String actionURL) {
+			this.name = name;
+			this.restTemplate = restTemplate;
+			this.actionURL = actionURL;
+		}
+
+		public RestTemplate getRestTemplate() {
+			return restTemplate;
+		}
+
+		public String getActionURL() {
+			return actionURL;
+		}
+
+		public String getName() {
+			return name;
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/GA4GHTokenParser.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/GA4GHTokenParser.java
new file mode 100644
index 000000000..a705506dd
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/elixir/GA4GHTokenParser.java
@@ -0,0 +1,64 @@
+package cz.muni.ics.oidc.server.elixir;
+
+import static cz.muni.ics.oidc.server.elixir.GA4GHClaimSource.parseAndVerifyVisa;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.jwt.SignedJWT;
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * This class is a command-line debugging tool. It parses JSON in GA4GH Passport format,
+ * verifies signatures on Passport Visas (JWT tokens), and prints them in human-readable format.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class GA4GHTokenParser {
+
+	static ObjectMapper jsonMapper = new ObjectMapper();
+
+	public static void main(String[] args) throws IOException, ParseException, JOSEException {
+		GA4GHClaimSource.parseConfigFile("ga4gh_config.yml");
+		String userinfo = "/tmp/ga4gh.json";
+		JsonNode doc = jsonMapper.readValue(new File(userinfo), JsonNode.class);
+		JsonNode ga4gh = doc.get("ga4gh_passport_v1");
+		long startx = System.currentTimeMillis();
+		System.out.println();
+		for (JsonNode jwtString : ga4gh) {
+			String s = jwtString.asText();
+			GA4GHClaimSource.PassportVisa visa = parseAndVerifyVisa(s);
+			if(!visa.isVerified()) {
+				System.out.println("visa not verified: "+s);
+				System.out.println("visa = " + visa.getPrettyString());
+
+//				System.exit(1);
+			} else {
+				System.out.println("OK: "+visa.getPrettyString());
+			}
+			SignedJWT jwt = (SignedJWT) JWTParser.parse(s);
+			ObjectWriter prettyPrinter = jsonMapper.writerWithDefaultPrettyPrinter();
+
+			JsonNode visaHeader = jsonMapper.readValue(jwt.getHeader().toString(), JsonNode.class);
+			System.out.println(prettyPrinter.writeValueAsString(visaHeader));
+
+			JsonNode visaPayload = jsonMapper.readValue(jwt.getPayload().toString(), JsonNode.class);
+			System.out.println(prettyPrinter.writeValueAsString(visaPayload));
+		}
+		long endx = System.currentTimeMillis();
+		System.out.println("signature verification time: " + (endx - startx));
+
+	}
+
+	private static String isoDateTime(long linuxTime) {
+		return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(ZonedDateTime.ofInstant(Instant.ofEpochSecond(linuxTime), ZoneId.systemDefault()));
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/CallPerunFiltersFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/CallPerunFiltersFilter.java
new file mode 100644
index 000000000..c4a37f603
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/CallPerunFiltersFilter.java
@@ -0,0 +1,86 @@
+package cz.muni.ics.oidc.server.filters;
+
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+import javax.annotation.PostConstruct;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.GenericFilterBean;
+
+/**
+ * This filter calls other Perun filters saved in the PerunFiltersContext
+ *
+ * @author Dominik Baranek <baranek@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class CallPerunFiltersFilter extends GenericFilterBean {
+
+    public static final Logger log = LoggerFactory.getLogger(CallPerunFiltersFilter.class);
+
+    @Autowired
+    private Properties coreProperties;
+
+    @Autowired
+    private BeanUtil beanUtil;
+
+    @Autowired
+    private OAuth2RequestFactory authRequestFactory;
+
+    @Autowired
+    private ClientDetailsEntityService clientDetailsEntityService;
+
+    @Autowired
+    private PerunAdapter perunAdapter;
+
+    private PerunFiltersContext perunFiltersContext;
+
+    @PostConstruct
+    public void postConstruct() {
+        this.perunFiltersContext = new PerunFiltersContext(coreProperties, beanUtil);
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+            throws IOException, ServletException
+    {
+        List<PerunRequestFilter> filters = perunFiltersContext.getFilters();
+        if (filters != null && !filters.isEmpty()) {
+            HttpServletRequest request = (HttpServletRequest) servletRequest;
+            ClientDetailsEntity client = FiltersUtils.extractClientFromRequest(request, authRequestFactory,
+                    clientDetailsEntityService);
+            Facility facility = null;
+            if (client != null && StringUtils.hasText(client.getClientId())) {
+                try {
+                    facility = perunAdapter.getFacilityByClientId(client.getClientId());
+                } catch (Exception e) {
+                    log.warn("{} - could not fetch facility for client_id '{}'",
+                            CallPerunFiltersFilter.class.getSimpleName(), client.getClientId(), e);
+                }
+            }
+            PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter);
+            FilterParams params = new FilterParams(client, facility, user);
+            for (PerunRequestFilter filter : filters) {
+                if (!filter.doFilter(servletRequest, servletResponse, params)) {
+                    return;
+                }
+            }
+        }
+        filterChain.doFilter(servletRequest, servletResponse);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FilterParams.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FilterParams.java
new file mode 100644
index 000000000..b0a92d366
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FilterParams.java
@@ -0,0 +1,39 @@
+package cz.muni.ics.oidc.server.filters;
+
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+
+public class FilterParams {
+
+    private final ClientDetailsEntity client;
+    private final Facility facility;
+    private final PerunUser user;
+
+    public FilterParams(ClientDetailsEntity client, Facility facility, PerunUser user) {
+        this.client = client;
+        this.facility = facility;
+        this.user = user;
+    }
+
+    public ClientDetailsEntity getClient() {
+        return client;
+    }
+
+    public Facility getFacility() {
+        return facility;
+    }
+
+    public PerunUser getUser() {
+        return user;
+    }
+
+    public String getClientIdentifier() {
+        if (client != null) {
+            return client.getClientId();
+        }
+
+        return null;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java
new file mode 100644
index 000000000..156b53aaf
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/FiltersUtils.java
@@ -0,0 +1,328 @@
+package cz.muni.ics.oidc.server.filters;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_FORCE_AUTHN;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.SAML_EPUID;
+import static org.apache.commons.lang3.StringUtils.isNotEmpty;
+
+import com.google.common.base.Strings;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.server.PerunPrincipal;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig;
+import cz.muni.ics.oidc.web.controllers.ControllerUtils;
+import cz.muni.ics.oidc.web.controllers.PerunUnapprovedRegistrationController;
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
+import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
+import org.springframework.security.saml.SAMLCredential;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+
+/**
+ * Utility class for filters. Contains common methods used by most of filter classes.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class FiltersUtils {
+
+	private static final Logger log = LoggerFactory.getLogger(FiltersUtils.class);
+
+	private static final RequestMatcher requestMatcher = new AntPathRequestMatcher(PerunFilterConstants.AUTHORIZE_REQ_PATTERN);
+
+	/**
+	 * Create map of request params in format key = name, value = paramValue.
+	 *
+	 * @param parameterMap Original map of parameters
+	 * @return Map of parameters
+	 */
+	public static Map<String, String> createRequestMap(Map<String, String[]> parameterMap) {
+		Map<String, String> requestMap = new HashMap<>();
+		for (String key : parameterMap.keySet()) {
+			String[] val = parameterMap.get(key);
+			if (val != null && val.length > 0) {
+				requestMap.put(key, val[0]); // add the first value only (which is what Spring seems to do)
+			}
+		}
+		return requestMap;
+	}
+
+	/**
+	 * Extract client from request
+	 *
+	 * @param request request to be matched and containing client
+	 * @param authRequestFactory authorization request factory
+	 * @param clientService service fetching client details
+	 * @return extracted client, null if some error occurs
+	 */
+	@SuppressWarnings("unchecked")
+	public static ClientDetailsEntity extractClientFromRequest(HttpServletRequest request,
+															   OAuth2RequestFactory authRequestFactory,
+															   ClientDetailsEntityService clientService)
+	{
+		if (!requestMatcher.matches(request) || request.getParameter("response_type") == null) {
+			return null;
+		}
+
+		AuthorizationRequest authRequest = authRequestFactory.createAuthorizationRequest(
+				FiltersUtils.createRequestMap(request.getParameterMap()));
+
+		ClientDetailsEntity client;
+		if (Strings.isNullOrEmpty(authRequest.getClientId())) {
+			log.debug("cannot extract client - ClientID is null or empty");
+			return null;
+		}
+
+		client = clientService.loadClientByClientId(authRequest.getClientId());
+		if (Strings.isNullOrEmpty(client.getClientName())) {
+			log.warn("cannot extract clientName for the clientID '{}'", client.getClientId());
+			return null;
+		}
+
+		log.debug("returning client '{}' with ID '{}'", client.getClientId(), client.getClientName());
+		return client;
+	}
+
+	/**
+	 * Get Perun user
+	 * @param request Request object
+	 * @param perunAdapter Adapter of Perun interface
+	 * @return Found PerunUser
+	 */
+	public static PerunUser getPerunUser(HttpServletRequest request, PerunAdapter perunAdapter) {
+		SAMLCredential samlCredential = getSamlCredential(request);
+		if (samlCredential == null) {
+			return null;
+		}
+		PerunPrincipal principal = getPerunPrincipal(samlCredential);
+		log.debug("fetching Perun user with extLogin '{}' and extSourceName '{}'",
+				principal.getExtLogin(), principal.getExtSourceName());
+		return perunAdapter.getPreauthenticatedUserId(principal);
+	}
+
+	public static SAMLCredential getSamlCredential(HttpServletRequest request) {
+		ExpiringUsernameAuthenticationToken p = (ExpiringUsernameAuthenticationToken) request.getUserPrincipal();
+		if (p == null) {
+			return null;
+		}
+		return (SAMLCredential) p.getCredentials();
+	}
+
+	public static PerunPrincipal getPerunPrincipal(SAMLCredential credential) {
+		String extLogin = credential.getAttributeAsString(SAML_EPUID);
+		String extSourceName = credential.getRemoteEntityID();
+		return new PerunPrincipal(extLogin, extSourceName);
+	}
+
+	/**
+	 * Extract PerunPrincipal from request
+	 * @param req request object
+	 * @param proxyExtSourceName name of proxy
+	 * @return extracted principal or null if not present
+	 */
+	public static PerunPrincipal extractPerunPrincipal(HttpServletRequest req, String proxyExtSourceName) {
+		String extLogin = null;
+		String remoteUser = req.getRemoteUser();
+		if (isNotEmpty(remoteUser)) {
+			extLogin = remoteUser;
+		} else if (req.getUserPrincipal() != null) {
+			extLogin = ((User)req.getUserPrincipal()).getUsername();
+		}
+
+		PerunPrincipal principal = null;
+		log.error("{}", req.getUserPrincipal());
+		log.error("{}", req.getRemoteUser());
+
+
+		if (extLogin != null) {
+			principal = new PerunPrincipal(extLogin, proxyExtSourceName);
+			log.debug("extracted principal '{}'", principal);
+		} else {
+			log.debug("could not extract principal");
+		}
+
+		return principal;
+	}
+
+	/**
+	 * Check if given scope has been requested
+	 * @param scopeParam Value of parameter "scope" from request
+	 * @param scope Name of scope to be found.
+	 * @return TRUE if present, false otherwise
+	 */
+	public static boolean isScopePresent(String scopeParam, String scope) {
+		if (scopeParam == null || scopeParam.trim().isEmpty()) {
+			log.trace("no scope has been requested");
+			return false;
+		}
+
+		String[] scopes = scopeParam.split(" ");
+		for (String s : scopes) {
+			if (s.equals(scope)) {
+				log.trace("scope '{}' has been requested", scope);
+				return true;
+			}
+		}
+		log.trace("scope has not been requested");
+		return false;
+	}
+
+	/**
+	 * Build URL of original request, remove forceAuthn parameter.
+	 * @param req request wrapper object
+	 * @return Rebuilt URL.
+	 */
+	public static String buildRequestURL(HttpServletRequest req) {
+		return buildRequestURL(req, null);
+	}
+
+	/**
+	 * Build URL of original request, remove forceAuthn parameter, add new parameters if passed.
+	 * @param req request wrapper object
+	 * @param additionalParams parameters to be added
+	 * @return Rebuilt URL.
+	 */
+	public static String buildRequestURL(HttpServletRequest req, Map<String, String> additionalParams) {
+		String returnURL = req.getRequestURL().toString();
+
+		if (req.getQueryString() != null) {
+			if (req.getQueryString().contains(PARAM_FORCE_AUTHN)) {
+				String queryStr = removeForceAuthParam(req.getQueryString());
+				returnURL += ('?' + queryStr);
+			} else {
+				returnURL += ('?' + req.getQueryString());
+			}
+
+			if (additionalParams != null) {
+				returnURL += ('&' + additionalParams.entrySet().stream()
+						.map(pair -> pair.getKey() + '=' + pair.getValue())
+						.collect(Collectors.joining("&")));
+			}
+		}
+		log.debug("returning rebuilt request URL: '{}'", returnURL);
+		return returnURL;
+	}
+
+	/**
+	 * Redirect user to the unapproved page.
+	 * @param request original request object
+	 * @param response response object
+	 * @param clientId identifier of the service
+	 */
+	public static void redirectUnapproved(HttpServletRequest request, HttpServletResponse response, String clientId, String redirectMapping)
+	{
+		// cannot register, redirect to unapproved
+		Map<String, String> params = new HashMap<>();
+		if (clientId != null) {
+			params.put("client_id", clientId);
+		}
+
+		String redirectUrl = ControllerUtils.createRedirectUrl(request, PerunFilterConstants.AUTHORIZE_REQ_PATTERN,
+				redirectMapping, params);
+		response.reset();
+		response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+		response.setHeader("Location", redirectUrl);
+	}
+
+	/**
+	 * Redirect user to the correct page when cannot access the service based on membership.
+	 * @param request Request object
+	 * @param response Response object
+	 * @param facility Facility representing the client
+	 * @param user User accessing the service
+	 * @param clientIdentifier ClientID
+	 * @param facilityAttrsConfig Config object for facility attributes
+	 * @param facilityAttributes Actual facility attributes
+	 * @param perunAdapter Adapter to call Perun
+	 */
+	public static void redirectUserCannotAccess(HttpServletRequest request,
+												HttpServletResponse response,
+												Facility facility,
+												PerunUser user,
+												String clientIdentifier,
+												FacilityAttrsConfig facilityAttrsConfig,
+												Map<String, PerunAttributeValue> facilityAttributes,
+												PerunAdapter perunAdapter,
+												String redirectUrl)
+	{
+		if (facilityAttributes.get(facilityAttrsConfig.getAllowRegistrationAttr()).valueAsBoolean()) {
+			boolean canRegister = perunAdapter.getAdapterRpc().groupWhereCanRegisterExists(facility);
+			if (canRegister) {
+				PerunAttributeValue customRegUrlAttr = facilityAttributes.get(facilityAttrsConfig.getRegistrationURLAttr());
+				if (customRegUrlAttr != null && customRegUrlAttr.valueAsString() != null) {
+					String customRegUrl = facilityAttributes.get(facilityAttrsConfig.getRegistrationURLAttr()).valueAsString();
+					customRegUrl = validateUrl(customRegUrl);
+					if (customRegUrl != null) {
+						// redirect to custom registration URL
+						FiltersUtils.redirectToCustomRegUrl(response, customRegUrl, user);
+						return;
+					}
+				}
+
+				if (facilityAttributes.get(facilityAttrsConfig.getDynamicRegistrationAttr()).valueAsBoolean()) {
+					// redirect to registration form
+					FiltersUtils.redirectToRegistrationForm(request, response, clientIdentifier, facility, user);
+					return;
+				}
+			}
+		}
+
+		// cannot register, redirect to unapproved
+		log.debug("user cannot register to obtain access, redirecting user '{}' to unapproved page", user);
+		FiltersUtils.redirectUnapproved(request, response, clientIdentifier, redirectUrl);
+	}
+
+	private static void redirectToRegistrationForm(HttpServletRequest request, HttpServletResponse response,
+												   String clientIdentifier, Facility facility, PerunUser user) {
+		Map<String, String> params = new HashMap<>();
+		params.put("client_id", clientIdentifier);
+		params.put("facility_id", facility.getId().toString());
+		params.put("user_id", String.valueOf(user.getId()));
+		String redirectUrl = ControllerUtils.createRedirectUrl(request, PerunFilterConstants.AUTHORIZE_REQ_PATTERN,
+				PerunUnapprovedRegistrationController.REGISTRATION_CONTINUE_MAPPING, params);
+		log.debug("redirecting user '{}' to the registration form URL: {}", user, redirectUrl);
+		response.reset();
+		response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+		response.setHeader("Location", redirectUrl);
+	}
+
+	private static void redirectToCustomRegUrl(HttpServletResponse response, String customRegUrl, PerunUser user) {
+		log.debug("redirecting user '{}' to the custom registration URL: {}", user, customRegUrl);
+		response.reset();
+		response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+		response.setHeader("Location", customRegUrl);
+	}
+
+	private static String validateUrl(String customRegUrl) {
+		return (customRegUrl == null || customRegUrl.isEmpty()) ? null : customRegUrl;
+	}
+
+	private static String removeForceAuthParam(String query) {
+		return Arrays.stream(query.split("&"))
+				.map(FiltersUtils::splitQueryParameter)
+				.filter(pair -> !PARAM_FORCE_AUTHN.equals(pair.getKey()))
+				.map(pair -> pair.getKey() + "=" + pair.getValue())
+				.collect(Collectors.joining("&"));
+	}
+
+	private static Map.Entry<String, String> splitQueryParameter(String it) {
+		final int idx = it.indexOf("=");
+		final String key = (idx > 0) ? it.substring(0, idx) : it;
+		final String value = (idx > 0 && it.length() > idx + 1) ? it.substring(idx + 1) : "";
+		return new AbstractMap.SimpleImmutableEntry<>(key, value);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFilterConstants.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFilterConstants.java
new file mode 100644
index 000000000..12401bb48
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFilterConstants.java
@@ -0,0 +1,40 @@
+package cz.muni.ics.oidc.server.filters;
+
+/**
+ * Class containing common constants used by Perun request filters.
+ *
+ * @author Dominik Baranek <baranek@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunFilterConstants {
+
+    public static final String AUTHORIZE_REQ_PATTERN = "/authorize";
+    public static final String SHIB_IDENTITY_PROVIDER = "Shib-Identity-Provider";
+    public static final String SHIB_AUTHN_CONTEXT_CLASS = "Shib-AuthnContext-Class";
+    public static final String SHIB_AUTHN_CONTEXT_METHOD = "Shib-Authentication-Method";
+
+    public static final String PARAM_CLIENT_ID = "client_id";
+    public static final String PARAM_SCOPE = "scope";
+    public static final String PARAM_MESSAGE = "message";
+    public static final String PARAM_HEADER = "header";
+    public static final String PARAM_TARGET = "target";
+    public static final String PARAM_FORCE_AUTHN = "forceAuthn";
+    public static final String PARAM_PROMPT = "prompt";
+    public static final String PARAM_REASON = "reason";
+    public static final String PARAM_ACCEPTED = "accepted";
+    public static final String PARAM_ACR_VALUES = "acr_values";
+    public static final String PARAM_POST_LOGOUT_REDIRECT_URI = "post_logout_redirect_uri";
+    public static final String PARAM_STATE = "state";
+    public static final String CLIENT_ID_PREFIX = "urn:cesnet:proxyidp:client_id:";
+    public static final String AARC_IDP_HINT = "aarc_idp_hint";
+
+    public static final String IDP_ENTITY_ID_PREFIX = "urn:cesnet:proxyidp:idpentityid:";
+    public static final String FILTER_PREFIX = "urn:cesnet:proxyidp:filter:";
+    public static final String EFILTER_PREFIX = "urn:cesnet:proxyidp:efilter:";
+
+    public static final String SAML_EPUID = "urn:oid:1.3.6.1.4.1.5923.1.1.1.13";
+    public static final String REFEDS_MFA = "https://refeds.org/profile/mfa";
+    public static final String PROMPT_LOGIN = "login";
+    public static final String PROMPT_SELECT_ACCOUNT = "select_account";
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFiltersContext.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFiltersContext.java
new file mode 100644
index 000000000..0dda78f45
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunFiltersContext.java
@@ -0,0 +1,93 @@
+package cz.muni.ics.oidc.server.filters;
+
+import cz.muni.ics.oidc.BeanUtil;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * Class that contains all custom Perun request filters. Filters are stored in the LinkedList
+ * and executed in the order they are added to the list.
+ *
+ * Filters are configured from configuration file in following way:
+ * filter.names=filterName1,filterName2,...
+ *
+ * @see PerunRequestFilter for configuration of filter
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunFiltersContext {
+
+	private static final Logger log = LoggerFactory.getLogger(PerunFiltersContext.class);
+
+	private static final String FILTER_NAMES = "filter.names";
+	private static final String FILTER_CLASS = ".class";
+	private static final String PREFIX = "filter.";
+
+	private List<PerunRequestFilter> filters;
+	private Properties properties;
+	private BeanUtil beanUtil;
+
+	public PerunFiltersContext(Properties properties, BeanUtil beanUtil) {
+		this.properties = properties;
+		this.beanUtil = beanUtil;
+		this.filters = new LinkedList<>();
+
+		String filterNames = properties.getProperty(FILTER_NAMES);
+		log.debug("Filters to be initialized '{}'", filterNames);
+
+		log.debug("--------------------------------");
+		for (String filterName: filterNames.split(",")) {
+			PerunRequestFilter requestFilter = loadFilter(filterName);
+			filters.add(requestFilter);
+			log.debug("--------------------------------");
+		}
+	}
+
+	public List<PerunRequestFilter> getFilters() {
+		return filters;
+	}
+
+	private PerunRequestFilter loadFilter(String filterName) {
+		String propPrefix = PerunFiltersContext.PREFIX + filterName;
+		String filterClass = properties.getProperty(propPrefix + FILTER_CLASS, null);
+		if (!StringUtils.hasText(filterClass)) {
+			log.warn("{} - failed to initialized filter: no class has ben configured", filterName);
+			return null;
+		}
+		log.trace("{} - loading class '{}'", filterName, filterClass);
+
+		try {
+			Class<?> rawClazz = Class.forName(filterClass);
+			if (!PerunRequestFilter.class.isAssignableFrom(rawClazz)) {
+				log.warn("{} - failed to initialized filter: class '{}' does not extend PerunRequestFilter",
+						filterName, filterClass);
+				return null;
+			}
+			
+			@SuppressWarnings("unchecked") Class<PerunRequestFilter> clazz = (Class<PerunRequestFilter>) rawClazz;
+			Constructor<PerunRequestFilter> constructor = clazz.getConstructor(PerunRequestFilterParams.class);
+			PerunRequestFilterParams params = new PerunRequestFilterParams(filterName, propPrefix, properties, beanUtil);
+			return constructor.newInstance(params);
+		} catch (ClassNotFoundException e) {
+			log.warn("{} - failed to initialize filter: class '{}' was not found", filterName, filterClass);
+			log.trace("{} - details:", filterName, e);
+			return null;
+		} catch (NoSuchMethodException e) {
+			log.warn("{} - failed to initialize filter: class '{}' does not have proper constructor",
+					filterName, filterClass);
+			log.trace("{} - details:", filterName, e);
+			return null;
+		} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+			log.warn("{} - failed to initialize filter: class '{}' cannot be instantiated", filterName, filterClass);
+			log.trace("{} - details:", filterName, e);
+			return null;
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilter.java
new file mode 100644
index 000000000..c8282fe3b
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilter.java
@@ -0,0 +1,110 @@
+package cz.muni.ics.oidc.server.filters;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.AUTHORIZE_REQ_PATTERN;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+
+/**
+ * Abstract class for Perun filters. All filters called in CallPerunFiltersFilter has to extend this.
+ *
+ * Configuration of filter names:
+ * <ul>
+ *     <li><b>filter.names</b> - comma separated list of names of the request filters</li>
+ * </ul>
+ *
+ * Configuration of filter (replace [name] part with the name defined for the filter):
+ * <ul>
+ *     <li><b>filter.[name].class</b> - Class the filter instantiates</li>
+ *     <li><b>filter.[name].subs</b> - comma separated list of sub values for which execution of filter will be skipped
+ *         if user's SUB is in the list</li>
+ *     <li><b>filter.[name].clientIds</b> - comma separated list of client_id values for which execution of filter
+ *         will be skipped if client_id is in the list</li>
+ * </ul>
+ *
+ * @see cz.muni.ics.oidc.server.filters.impl package for specific filters and their configuration
+ *
+ * @author Dominik Baranek <baranek@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public abstract class PerunRequestFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunRequestFilter.class);
+
+    private static final String DELIMITER = ",";
+    private static final String CLIENT_IDS = "clientIds";
+    private static final String SUBS = "subs";
+
+    private static final RequestMatcher requestMatcher = new AntPathRequestMatcher(AUTHORIZE_REQ_PATTERN);
+
+    private final String filterName;
+    private Set<String> clientIds = new HashSet<>();
+    private Set<String> subs = new HashSet<>();
+
+    public PerunRequestFilter(PerunRequestFilterParams params) {
+        filterName = params.getFilterName();
+
+        if (params.hasProperty(CLIENT_IDS)) {
+            this.clientIds = new HashSet<>(Arrays.asList(params.getProperty(CLIENT_IDS).split(DELIMITER)));
+        }
+
+        if (params.hasProperty(SUBS)) {
+            this.subs = new HashSet<>(Arrays.asList(params.getProperty(SUBS).split(DELIMITER)));
+        }
+
+        log.debug("{} - filter initialized", filterName);
+        log.debug("{} - skip execution for users with SUB in: {}", filterName, subs);
+        log.debug("{} - skip execution for clients with CLIENT_ID in: {}", filterName, clientIds);
+    }
+
+    /**
+     * In this method is done whole logic of filer
+     *
+     * @param request request
+     * @param response response
+     * @return boolean if filter was successfully done
+     * @throws IOException this exception could be thrown because of failed or interrupted I/O operation
+     */
+    protected abstract boolean process(ServletRequest request, ServletResponse response, FilterParams params)
+            throws IOException;
+
+    public boolean doFilter(ServletRequest req, ServletResponse res, FilterParams params) throws IOException {
+        HttpServletRequest request = (HttpServletRequest) req;
+        // skip everything that's not an authorize URL
+        if (!requestMatcher.matches(request)) {
+            log.debug("{} - filter has been skipped, did not match '/authorize' the request", filterName);
+            return true;
+        }
+        if (!skip(request)) {
+            log.trace("{} - executing filter", filterName);
+            return this.process(req, res, params);
+        } else {
+            return true;
+        }
+    }
+
+    private boolean skip(HttpServletRequest request) {
+        String sub = (request.getUserPrincipal() != null) ? request.getUserPrincipal().getName() : null;
+        String clientId = request.getParameter(PerunFilterConstants.PARAM_CLIENT_ID);
+
+        if (sub != null && subs.contains(sub)) {
+            log.debug("{} - skip filter execution: matched one of the ignored SUBS ({})", filterName, sub);
+            return true;
+        } else if (clientId != null && clientIds.contains(clientId)){
+            log.debug("{} - skip filter execution: matched one of the ignored CLIENT_IDS ({})", filterName, clientId);
+            return true;
+        }
+
+        return false;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilterParams.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilterParams.java
new file mode 100644
index 000000000..10119b4bc
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/PerunRequestFilterParams.java
@@ -0,0 +1,45 @@
+package cz.muni.ics.oidc.server.filters;
+
+import cz.muni.ics.oidc.BeanUtil;
+import java.util.Properties;
+
+/**
+ * Class holding parameters for filter instantiation
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunRequestFilterParams {
+
+	private String filterName;
+
+	private String propertyPrefix;
+	private Properties properties;
+	private BeanUtil beanUtil;
+
+	public PerunRequestFilterParams(String filterName, String propertyPrefix, Properties properties, BeanUtil beanUtil) {
+		this.filterName = filterName;
+		this.propertyPrefix = propertyPrefix;
+		this.properties = properties;
+		this.beanUtil = beanUtil;
+	}
+
+	public boolean hasProperty(String name) {
+		return this.properties.containsKey(propertyPrefix + '.' + name);
+	}
+
+	public String getProperty(String name) {
+		return this.properties.getProperty(propertyPrefix + '.' + name);
+	}
+
+	public BeanUtil getBeanUtil() {
+		return beanUtil;
+	}
+
+	public String getFilterName() {
+		return filterName;
+	}
+
+	public Properties getProperties() {
+		return properties;
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/MultiMDCFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/MultiMDCFilter.java
new file mode 100644
index 000000000..e4980c8ee
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/MultiMDCFilter.java
@@ -0,0 +1,33 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import cz.muni.ics.oidc.server.filters.impl.mdc.RemoteAddressMDCFilter;
+import cz.muni.ics.oidc.server.filters.impl.mdc.SessionIdMDCFilter;
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import org.slf4j.MDC;
+import org.springframework.web.filter.GenericFilterBean;
+
+public class MultiMDCFilter extends GenericFilterBean {
+
+    private final RemoteAddressMDCFilter remoteAddressMDCFilter;
+    private final SessionIdMDCFilter sessionIdMDCFilter;
+
+    public MultiMDCFilter() {
+        this.remoteAddressMDCFilter = new RemoteAddressMDCFilter();
+        this.sessionIdMDCFilter = new SessionIdMDCFilter();
+    }
+
+    @Override
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+            throws IOException, ServletException
+    {
+        remoteAddressMDCFilter.doFilter(servletRequest);
+        sessionIdMDCFilter.doFilter(servletRequest);
+        filterChain.doFilter(servletRequest, servletResponse);
+        MDC.clear();
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunAuthorizationFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunAuthorizationFilter.java
new file mode 100644
index 000000000..3f574f1e3
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunAuthorizationFilter.java
@@ -0,0 +1,93 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig;
+import cz.muni.ics.oidc.server.filters.FilterParams;
+import cz.muni.ics.oidc.server.filters.FiltersUtils;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilterParams;
+import cz.muni.ics.oidc.web.controllers.PerunUnapprovedController;
+import java.util.Map;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Authorization filter. Decides if user can access the service based on his/hers
+ * membership in the groups assigned to the Perun facility resources. Facility represents
+ * client in this context.
+ *
+ * Configuration:
+ * - based on the configuration of bean "facilityAttrsConfig"
+ * @see FacilityAttrsConfig
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunAuthorizationFilter extends PerunRequestFilter {
+
+	private static final Logger log = LoggerFactory.getLogger(PerunAuthorizationFilter.class);
+
+	private final PerunAdapter perunAdapter;
+	private final FacilityAttrsConfig facilityAttrsConfig;
+	private final String filterName;
+
+	public PerunAuthorizationFilter(PerunRequestFilterParams params) {
+		super(params);
+		BeanUtil beanUtil = params.getBeanUtil();
+		this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
+		this.facilityAttrsConfig = beanUtil.getBean(FacilityAttrsConfig.class);
+		this.filterName = params.getFilterName();
+	}
+
+	@Override
+	protected boolean process(ServletRequest req, ServletResponse res, FilterParams params) {
+		HttpServletRequest request = (HttpServletRequest) req;
+		HttpServletResponse response = (HttpServletResponse) res;
+
+		Facility facility = params.getFacility();
+		if (facility == null || facility.getId() == null) {
+			log.debug("{} - skip filter execution: no facility provided", filterName);
+			return true;
+		}
+
+		PerunUser user = params.getUser();
+		if (user == null || user.getId() == null) {
+			log.debug("{} - skip filter execution: no user provided", filterName);
+			return true;
+		}
+
+		return this.decideAccess(facility, user, request, response, params.getClientIdentifier(),
+				perunAdapter, facilityAttrsConfig);
+	}
+
+	private boolean decideAccess(Facility facility, PerunUser user, HttpServletRequest request,
+								 HttpServletResponse response, String clientIdentifier, PerunAdapter perunAdapter,
+								 FacilityAttrsConfig facilityAttrsConfig)
+	{
+		Map<String, PerunAttributeValue> facilityAttributes = perunAdapter.getFacilityAttributeValues(
+				facility, facilityAttrsConfig.getMembershipAttrNames());
+
+		if (!facilityAttributes.get(facilityAttrsConfig.getCheckGroupMembershipAttr()).valueAsBoolean()) {
+			log.debug("{} - skip filter execution: membership check not requested", filterName);
+			return true;
+		}
+
+		if (perunAdapter.canUserAccessBasedOnMembership(facility, user.getId())) {
+			log.info("{} - user allowed to access the service", filterName);
+			return true;
+		} else {
+			FiltersUtils.redirectUserCannotAccess(request, response, facility, user, clientIdentifier,
+					facilityAttrsConfig, facilityAttributes, perunAdapter,
+					PerunUnapprovedController.UNAPPROVED_AUTHORIZATION);
+			return false;
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunEnsureVoMember.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunEnsureVoMember.java
new file mode 100644
index 000000000..0d027535f
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunEnsureVoMember.java
@@ -0,0 +1,190 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.server.filters.FilterParams;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilterParams;
+import cz.muni.ics.oidc.web.controllers.ControllerUtils;
+import cz.muni.ics.oidc.web.controllers.PerunUnapprovedController;
+import cz.muni.ics.oidc.web.controllers.RegistrationController;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.http.HttpHeaders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * This filter forwards user to a warning page if the service is in test environment.
+ * Otherwise, user can to access the service.
+ *
+ * Configuration (replace [name] part with the name defined for the filter):
+ * <ul>
+ *     <li><b>filter.[name].triggerAttr</b> - mapping to attribute which contains flag if this is enabled for facility</li>
+ *     <li><b>filter.[name].voDefsAttr</b> - mapping to attribute which contains VO(s) to check</li>
+ *     <li><b>filter.[name].loginURL</b> - mapping to the attribute containing service login URL</li>
+ * </ul>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunEnsureVoMember extends PerunRequestFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunEnsureVoMember.class);
+
+    private static final String TRIGGER_ATTR = "triggerAttr";
+    private static final String VO_DEFS_ATTR = "voDefsAttr";
+    private static final String LOGIN_URL_ATTR = "loginURL";
+
+    private final String triggerAttr;
+    private final String voDefsAttr;
+    private final String loginUrlAttr;
+    private final PerunAdapter perunAdapter;
+    private final String filterName;
+    private final PerunOidcConfig perunOidcConfig;
+
+    public PerunEnsureVoMember(PerunRequestFilterParams params) {
+        super(params);
+        BeanUtil beanUtil = params.getBeanUtil();
+        this.perunOidcConfig = beanUtil.getBean(PerunOidcConfig.class);
+        this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
+        this.filterName = params.getFilterName();
+        this.triggerAttr = params.getProperty(TRIGGER_ATTR);
+        if (!StringUtils.hasText(triggerAttr)) {
+            throw new IllegalArgumentException("No value configured for '" + TRIGGER_ATTR + "' in filter " + filterName);
+        }
+        this.voDefsAttr = params.getProperty(VO_DEFS_ATTR);
+        if (!StringUtils.hasText(voDefsAttr)) {
+            throw new IllegalArgumentException("No value configured for '" + VO_DEFS_ATTR + "' in filter " + filterName);
+        }
+        this.loginUrlAttr = params.getProperty(LOGIN_URL_ATTR);
+        log.debug("{} - initialized filter: {}", filterName, this);
+    }
+
+    @Override
+    protected boolean process(ServletRequest req, ServletResponse res, FilterParams params) throws IOException {
+        HttpServletResponse response = (HttpServletResponse) res;
+
+        Facility facility = params.getFacility();
+        if (facility == null || facility.getId() == null) {
+            log.debug("{} - skip execution: no facility provided", filterName);
+            return true;
+        }
+
+        Map<String, PerunAttributeValue> attrs = perunAdapter.getFacilityAttributeValues(facility,
+                Arrays.asList(voDefsAttr, triggerAttr, loginUrlAttr));
+
+        PerunAttributeValue triggerAttrValue = attrs.getOrDefault(triggerAttr, null);
+        if (triggerAttrValue == null || !triggerAttrValue.valueAsBoolean()) {
+            log.debug("{} - skip execution: attribute '{}' is null or false, which disables the filter",
+                    filterName, triggerAttr);
+            return true;
+        }
+
+        PerunAttributeValue voDefsAttrValue = getVoDefsAttrValue(attrs.getOrDefault(voDefsAttr, null));
+        if (voDefsAttrValue == null) {
+            log.debug("{} - skip execution: attribute '{}' has null or no value", filterName, voDefsAttr);
+            return true;
+        }
+        String voShortName = voDefsAttrValue.valueAsString();
+
+        boolean canAccess = perunAdapter.isUserInVo(params.getUser().getId(), voShortName);
+
+        if (canAccess) {
+            log.debug("{} - user allowed to continue", filterName);
+            return true;
+        } else {
+            redirect(response, getLoginUrl(facility.getId()), voShortName);
+            return false;
+        }
+    }
+
+    private void redirect(HttpServletResponse response, PerunAttributeValue loginUrlAttr, String voShortName) {
+        String loginUrl = null;
+        if (loginUrlAttr != null && StringUtils.hasText(loginUrlAttr.valueAsString())) {
+            loginUrl = loginUrlAttr.valueAsString();
+        }
+        if (StringUtils.hasText(voShortName) && perunAdapter.getAdapterRpc().hasApplicationForm(voShortName)) {
+            redirectDirectly(response, loginUrl, voShortName);
+        } else {
+            redirectUnapproved(response);
+        }
+    }
+
+    private PerunAttributeValue getLoginUrl(Long facilityId) {
+        if (loginUrlAttr != null) {
+            return perunAdapter.getFacilityAttributeValue(facilityId, loginUrlAttr);
+        }
+        return null;
+    }
+
+    private PerunAttributeValue getVoDefsAttrValue(PerunAttributeValue attrValue) {
+        if (attrValue == null) {
+            return null;
+        } else if (attrValue.valueAsJson().isArray() && attrValue.valueAsJson().size() < 1) {
+            return null;
+        }
+        return attrValue;
+    }
+
+    private boolean canAccess(PerunAttributeValue attrValue, Set<String> memberShortNames) {
+        if (attrValue.valueAsJson().isArray()) {
+            Set<String> val = attrValue.valueAsList() == null ?
+                    Collections.emptySet() : new HashSet<>(attrValue.valueAsList());
+            return !Collections.disjoint(val, memberShortNames);
+        } else {
+            String val = attrValue.valueAsString();
+            return memberShortNames.contains(val);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "PerunEnsureVoMember{" +
+                "voDefsAttr='" + voDefsAttr + '\'' +
+                ", loginUrlAttr='" + loginUrlAttr + '\'' +
+                '}';
+    }
+
+    private void redirectDirectly(HttpServletResponse res, String loginUrl, String voShortName) {
+        String registrarUrl = perunOidcConfig.getRegistrarUrl();
+        Map<String, String> params = new HashMap<>();
+        params.put("vo", voShortName);
+        if (StringUtils.hasText(loginUrl)) {
+            params.put("targetnew", loginUrl);
+            params.put("targetexisting", loginUrl);
+        }
+        String target = ControllerUtils.createUrl(registrarUrl, params);
+
+        String url = ControllerUtils.constructRequestUrl(perunOidcConfig, RegistrationController.CONTINUE_DIRECT_MAPPING);
+        params.clear();
+        params.put(RegistrationController.PARAM_TARGET, target);
+
+        String redirectUrl = ControllerUtils.createUrl(url, params);
+        log.debug("{} - redirecting user to '{}'", filterName, redirectUrl);
+        res.reset();
+        res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+        res.setHeader(HttpHeaders.LOCATION, redirectUrl);
+    }
+
+    private void redirectUnapproved(HttpServletResponse res) {
+        String redirectUrl = ControllerUtils.constructRequestUrl(perunOidcConfig,
+                PerunUnapprovedController.UNAPPROVED_ENSURE_VO_MAPPING);
+
+        log.debug("{} - redirecting user to '{}'", filterName, redirectUrl);
+        res.reset();
+        res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+        res.setHeader(HttpHeaders.LOCATION, redirectUrl);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunForceAupFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunForceAupFilter.java
new file mode 100644
index 000000000..ab62c0b85
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunForceAupFilter.java
@@ -0,0 +1,323 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import static cz.muni.ics.oidc.web.controllers.AupController.APPROVED;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.models.Aup;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunAttribute;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.server.filters.FilterParams;
+import cz.muni.ics.oidc.server.filters.FiltersUtils;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilterParams;
+import cz.muni.ics.oidc.web.controllers.AupController;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * AUP filter checks if there are new AUPs which user hasn't accepted yet and forces him to do that.
+ *
+ * Configuration (replace [name] part with the name defined for the filter):
+ * <ul>
+ *     <li><b>filter.[name].orgAupsAttrName</b> - Mapping to Perun entityless attribute containing organization AUPs</li>
+ *     <li><b>filter.[name].userAupsAttrName</b> - Mapping to Perun user attribute containing list of AUPS approved by user</li>
+ *     <li><b>filter.[name].voAupAttrName</b> - Mapping to Perun VO attribute containing AUP specific for VO</li>
+ *     <li><b>filter.[name].facilityRequestedAupsAttrName</b> - Mapping to Perun facility attribute containing list of AUPs requested
+ *         by the service. Contains only keys for those AUPs</li>
+ *     <li><b>filter.[name].voShortNamesAttrName</b> - Mapping to Perun facility attribute containing list of short names for VOs
+ *         that have a resource assigned to the facility</li>
+ * </ul>
+ *
+ * @author Dominik Baranek <baranek@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunForceAupFilter extends PerunRequestFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunForceAupFilter.class);
+    private static final String DATE_FORMAT = "yyyy-MM-dd";
+
+    /* CONFIGURATION PROPERTIES */
+    private static final String ORG_AUPS_ATTR_NAME = "orgAupsAttrName";
+    private static final String USER_AUPS_ATTR_NAME = "userAupsAttrName";
+    private static final String VO_AUP_ATTR_NAME = "voAupAttrName";
+    private static final String FACILITY_REQUESTED_AUPS_ATTR_NAME = "facilityRequestedAupsAttrName";
+    private static final String VO_SHORT_NAMES_ATTR_NAME = "voShortNamesAttrName";
+
+    private final String perunOrgAupsAttrName;
+    private final String perunUserAupsAttrName;
+    private final String perunVoAupAttrName;
+    private final String perunFacilityRequestedAupsAttrName;
+    private final String perunFacilityVoShortNamesAttrName;
+    /* END OF CONFIGURATION PROPERTIES */
+
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    private final PerunAdapter perunAdapter;
+    private final PerunOidcConfig perunOidcConfig;
+    private final String filterName;
+
+    public PerunForceAupFilter(PerunRequestFilterParams params) {
+        super(params);
+        BeanUtil beanUtil = params.getBeanUtil();
+        this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
+        this.perunOidcConfig = beanUtil.getBean(PerunOidcConfig.class);
+
+        this.perunOrgAupsAttrName = params.getProperty(ORG_AUPS_ATTR_NAME);
+        this.perunUserAupsAttrName = params.getProperty(USER_AUPS_ATTR_NAME);
+        this.perunVoAupAttrName = params.getProperty(VO_AUP_ATTR_NAME);
+        this.perunFacilityRequestedAupsAttrName = params.getProperty(FACILITY_REQUESTED_AUPS_ATTR_NAME);
+        this.perunFacilityVoShortNamesAttrName = params.getProperty(VO_SHORT_NAMES_ATTR_NAME);
+        this.filterName = params.getFilterName();
+    }
+
+    @Override
+    protected boolean process(ServletRequest req, ServletResponse res, FilterParams params) throws IOException {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+
+        if (request.getSession() != null && request.getSession().getAttribute(APPROVED) != null) {
+            request.getSession().removeAttribute(APPROVED);
+            log.debug("{} - skip filter execution: aups are already approved, check at next access to the service due" +
+                    " to a delayed propagation to LDAP", filterName);
+            return true;
+        }
+
+        PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter);
+        if (user == null || user.getId() == null) {
+            log.debug("{} - skip filter execution: no user provider", filterName);
+            return true;
+        }
+
+        Facility facility = params.getFacility();
+        if (facility == null || facility.getId() == null) {
+            log.debug("{} - skip filter execution: no facility provider", filterName);
+            return true;
+        }
+
+        List<String> attrsToFetch = new ArrayList<>(
+                Arrays.asList(perunFacilityRequestedAupsAttrName, perunFacilityVoShortNamesAttrName));
+        Map<String, PerunAttributeValue> facilityAttributes = perunAdapter.getFacilityAttributeValues(facility, attrsToFetch);
+
+        if (facilityAttributes == null) {
+            log.debug("{} - skip filter execution: could not fetch attributes '{}' for facility '{}'",
+                    filterName, attrsToFetch, facility);
+            return true;
+        } else if (!facilityAttributes.containsKey(perunFacilityRequestedAupsAttrName) &&
+                !facilityAttributes.containsKey(perunFacilityVoShortNamesAttrName))
+        {
+            log.debug("{} - skip filter execution: could not fetch required attributes '{}' and '{}' for facility '{}'",
+                    filterName, perunFacilityRequestedAupsAttrName, perunFacilityVoShortNamesAttrName, facility);
+            return true;
+        }
+
+        Map<String, Aup> newAups;
+
+        try {
+            newAups = getAupsToApprove(user, facilityAttributes);
+        } catch (ParseException | IOException e) {
+            log.warn("{} - caught parse exception when processing AUPs to approve", filterName);
+            log.trace("{} - details:", filterName, e);
+            return true;
+        }
+
+        if (!newAups.isEmpty()) {
+            log.debug("{} - user has to approve some AUPs", filterName);
+            log.trace("{} - AUPS to be approved: '{}'", filterName, newAups);
+            String newAupsString = mapper.writeValueAsString(newAups);
+
+            request.getSession().setAttribute(AupController.RETURN_URL, request.getRequestURI()
+                    .replace(request.getContextPath(), "") + '?' + request.getQueryString());
+            request.getSession().setAttribute(AupController.NEW_AUPS, newAupsString);
+            request.getSession().setAttribute(AupController.USER_ATTR, perunUserAupsAttrName);
+
+            log.debug("{} - redirecting user '{}' to AUPs approval page", filterName, user);
+            response.sendRedirect(request.getContextPath() + '/' + AupController.URL);
+            return false;
+        }
+
+        log.debug("{} - no need to approve any AUPs", filterName);
+        return true;
+    }
+
+    private Map<String, Aup> getAupsToApprove(PerunUser user, Map<String, PerunAttributeValue> facilityAttributes)
+            throws ParseException, IOException
+    {
+        Map<String, Aup> aupsToApprove= new LinkedHashMap<>();
+
+        PerunAttributeValue userAupsAttr = perunAdapter.getUserAttributeValue(user.getId(), perunUserAupsAttrName);
+        if (perunOidcConfig.isFillMissingUserAttrs() && (userAupsAttr == null || userAupsAttr.isNullValue())) {
+            userAupsAttr = perunAdapter.getAdapterFallback().getUserAttributeValue(user.getId(), perunUserAupsAttrName);
+        }
+        Map<String, List<Aup>> userAups = convertToMapKeyToListOfAups(userAupsAttr.valueAsMap());
+
+        PerunAttributeValue requestedAupsAttr = facilityAttributes.get(perunFacilityRequestedAupsAttrName);
+        PerunAttributeValue facilityVoShortNamesAttr = facilityAttributes.get(perunFacilityVoShortNamesAttrName);
+
+        if (requestedAupsAttr != null && !requestedAupsAttr.isNullValue() && requestedAupsAttr.valueAsList() != null
+                && !requestedAupsAttr.valueAsList().isEmpty()) {
+            Map<String, Aup> orgAupsToApprove = getOrgAupsToApprove(requestedAupsAttr.valueAsList(), userAups);
+            mergeAupMaps(aupsToApprove, orgAupsToApprove);
+        }
+
+        if (facilityVoShortNamesAttr != null && !facilityVoShortNamesAttr.isNullValue()
+                && facilityVoShortNamesAttr.valueAsList() != null && !facilityVoShortNamesAttr.valueAsList().isEmpty()) {
+            Map<String, Aup> voAupsToApprove = getVoAupsToApprove(facilityVoShortNamesAttr.valueAsList(), userAups);
+            mergeAupMaps(aupsToApprove, voAupsToApprove);
+        }
+
+        return aupsToApprove;
+    }
+
+    private void mergeAupMaps(Map<String, Aup> original, Map<String, Aup> updates) {
+        for (Map.Entry<String, Aup> pair: updates.entrySet()) {
+            if (original.containsKey(pair.getKey())) {
+                Aup originalAup = original.get(pair.getKey());
+                Aup updateAup = pair.getValue();
+                if (updateAup.getDateAsLocalDate().isAfter(originalAup.getDateAsLocalDate())) {
+                    original.replace(pair.getKey(), pair.getValue());
+                }
+            } else {
+                original.put(pair.getKey(), pair.getValue());
+            }
+        }
+    }
+
+    private Map<String, Aup> getVoAupsToApprove(List<String> facilityVoShortNames, Map<String, List<Aup>> userAups)
+            throws IOException, ParseException {
+        Map<String, Aup> aupsToApprove = new LinkedHashMap<>();
+        Map<String, List<Aup>> voAups = getVoAups(facilityVoShortNames);
+
+        if (!voAups.isEmpty()) {
+            for (Map.Entry<String, List<Aup>> keyToVoAup : voAups.entrySet()) {
+                Aup voLatestAup = getLatestAupFromList(keyToVoAup.getValue());
+                if (userAups.containsKey(keyToVoAup.getKey())) {
+                    Aup userLatestAup = getLatestAupFromList(userAups.get(keyToVoAup.getKey()));
+                    if (! (voLatestAup.getDateAsLocalDate().isAfter(userLatestAup.getDateAsLocalDate()))) {
+                        continue;
+                    }
+                }
+                log.debug("{} - need to approve AUP with key '{}' ({})", filterName, keyToVoAup.getKey(), voLatestAup);
+                aupsToApprove.put(keyToVoAup.getKey(), voLatestAup);
+            }
+        }
+
+        log.trace("{} - VO AUPs to approve: {}", filterName, aupsToApprove);
+        return aupsToApprove;
+    }
+
+    private Map<String, Aup> getOrgAupsToApprove(List<String > requestedAups, Map<String, List<Aup>> userAups)
+            throws ParseException, IOException
+    {
+        Map<String, Aup> aupsToApprove = new LinkedHashMap<>();
+        Map<String, List<Aup>> orgAups = new HashMap<>();
+
+        Map<String, PerunAttribute> orgAupsAttr = perunAdapter.getAdapterRpc()
+                .getEntitylessAttributes(perunOrgAupsAttrName);
+
+        if (orgAupsAttr != null && !orgAupsAttr.isEmpty()) {
+            for (Map.Entry<String, PerunAttribute> entry : orgAupsAttr.entrySet()) {
+                if (entry.getValue() != null && entry.getValue().valueAsString() != null) {
+                    List<Aup> aups = Arrays.asList(mapper.readValue(entry.getValue().valueAsString(), Aup[].class));
+                    orgAups.put(entry.getKey(), aups);
+                }
+            }
+        }
+        log.debug("{} - Mapped ORG aups: {}", filterName, orgAups);
+
+        if (!orgAups.isEmpty()) {
+            for (String requiredOrgAupKey : requestedAups) {
+                if (!orgAups.containsKey(requiredOrgAupKey) || orgAups.get(requiredOrgAupKey) == null) {
+                    continue;
+                }
+                Aup orgLatestAup = getLatestAupFromList(orgAups.get(requiredOrgAupKey));
+                if (userAups.containsKey(requiredOrgAupKey)) {
+                    Aup userLatestAup = getLatestAupFromList(userAups.get(requiredOrgAupKey));
+                    if (!(orgLatestAup.getDateAsLocalDate().isAfter(userLatestAup.getDateAsLocalDate()))) {
+                        continue;
+                    }
+                }
+                log.debug("{} - need to approve AUP with key '{}' ({})", filterName, requiredOrgAupKey, orgLatestAup);
+                aupsToApprove.put(requiredOrgAupKey, orgLatestAup);
+            }
+        }
+
+        log.debug("{} - ORG AUPs to approve: {}", filterName, aupsToApprove);
+        return aupsToApprove;
+    }
+
+    private Map<String, List<Aup>> getVoAups(List<String> voShortNames) throws IOException {
+        Map<String, List<Aup>> voAups = new HashMap<>();
+
+        if (voShortNames != null && !voShortNames.isEmpty()) {
+            for (String voShortName : voShortNames) {
+                Long voId = perunAdapter.getVoByShortName(voShortName).getId();
+
+                PerunAttributeValue voAupAttr = perunAdapter.getVoAttributeValue(voId, perunVoAupAttrName);
+                if (voAupAttr == null || voAupAttr.valueAsString() == null) {
+                    continue;
+                }
+
+                if (StringUtils.hasText(voAupAttr.valueAsString())) {
+                    List<Aup> aups = Arrays.asList(mapper.readValue(voAupAttr.valueAsString(), Aup[].class));
+                    if (!aups.isEmpty()) {
+                        voAups.put(voShortName, aups);
+                    }
+                }
+            }
+        }
+
+        return voAups;
+    }
+
+    private Map<String, List<Aup>> convertToMapKeyToListOfAups(Map<String, String> keyToListOfAupsString)
+            throws IOException
+    {
+        Map<String, List<Aup>> resultMap = new HashMap<>();
+        if (keyToListOfAupsString != null && !keyToListOfAupsString.isEmpty()) {
+            for (Map.Entry<String, String> entry : keyToListOfAupsString.entrySet()) {
+                List<Aup> aups = Arrays.asList(mapper.readValue(entry.getValue(), Aup[].class));
+                resultMap.put(entry.getKey(), aups);
+            }
+        }
+        return resultMap;
+    }
+
+    private Aup getLatestAupFromList(List<Aup> aups) throws ParseException {
+        Aup latestAup = aups.get(0);
+
+        for (Aup aup : aups) {
+            Date latestAupDate = new SimpleDateFormat(DATE_FORMAT).parse(latestAup.getDate());
+            Date aupDate = new SimpleDateFormat(DATE_FORMAT).parse(aup.getDate());
+            log.info("latestAupDate({}): {}", latestAup, latestAupDate);
+            log.info("aupDate({}): {}", aup, aupDate);
+            if (latestAupDate.before(aupDate)) {
+                log.info("before");
+                latestAup = aup;
+            }
+        }
+
+        log.info("latestAup: {}", latestAup);
+        return latestAup;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsCesnetEligibleFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsCesnetEligibleFilter.java
new file mode 100644
index 000000000..6dc0a68da
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsCesnetEligibleFilter.java
@@ -0,0 +1,145 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_FORCE_AUTHN;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_REASON;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_SCOPE;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_TARGET;
+import static cz.muni.ics.oidc.web.controllers.PerunUnapprovedController.REASON_EXPIRED;
+import static cz.muni.ics.oidc.web.controllers.PerunUnapprovedController.REASON_NOT_SET;
+
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.filters.FilterParams;
+import cz.muni.ics.oidc.server.filters.FiltersUtils;
+import cz.muni.ics.oidc.server.filters.PerunFilterConstants;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilterParams;
+import cz.muni.ics.oidc.web.controllers.ControllerUtils;
+import cz.muni.ics.oidc.web.controllers.PerunUnapprovedController;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.http.HttpHeaders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This filter verifies that user attribute isCesnetEligible is not older than given time frame.
+ * In case the value is older, denies access to the service and forces user to use verified identity.
+ * Otherwise, user can to access the service.
+ *
+ * Configuration (replace [name] part with the name defined for the filter):
+ * <ul>
+ *     <li><b>filter.[name].isCesnetEligibleAttr</b> - mapping to isCesnetEligible attribute</li>
+ *     <li><b>filter.[name].validityPeriod</b> - specify in months, how long the value can be old, if no value
+ *         or invalid value has been provided, defaults to 12 months</li>
+ * </ul>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class PerunIsCesnetEligibleFilter extends PerunRequestFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunIsCesnetEligibleFilter.class);
+
+    /* CONFIGURATION PROPERTIES */
+    private static final String IS_CESNET_ELIGIBLE_ATTR_NAME = "isCesnetEligibleAttr";
+    private static final String IS_CESNET_ELIGIBLE_SCOPE = "isCesnetEligibleScope";
+    private static final String VALIDITY_PERIOD = "validityPeriod";
+    private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    private final String isCesnetEligibleAttrName;
+    private final String triggerScope;
+    private final int validityPeriod;
+    /* END OF CONFIGURATION PROPERTIES */
+
+    private final PerunAdapter perunAdapter;
+    private final String filterName;
+
+    public PerunIsCesnetEligibleFilter(PerunRequestFilterParams params) {
+        super(params);
+        BeanUtil beanUtil = params.getBeanUtil();
+        this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
+        this.isCesnetEligibleAttrName = params.getProperty(IS_CESNET_ELIGIBLE_ATTR_NAME);
+        this.triggerScope = params.getProperty(IS_CESNET_ELIGIBLE_SCOPE);
+        int validityPeriodParam = 12;
+        if (params.hasProperty(VALIDITY_PERIOD)) {
+            try {
+                validityPeriodParam = Integer.parseInt(params.getProperty(VALIDITY_PERIOD));
+            } catch (NumberFormatException ignored) {
+                //no problem, we have default value
+            }
+        }
+
+        this.validityPeriod = validityPeriodParam;
+        this.filterName = params.getFilterName();
+    }
+
+    @Override
+    protected boolean process(ServletRequest req, ServletResponse res, FilterParams params) {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+
+        if (!FiltersUtils.isScopePresent(request.getParameter(PARAM_SCOPE), triggerScope)) {
+            log.debug("{} - skip execution: scope '{}' is not present in request", filterName, triggerScope);
+            return true;
+        }
+
+        PerunUser user = params.getUser();
+        if (user == null || user.getId() == null) {
+            log.debug("{} - skip execution: no user provider", filterName);
+            return true;
+        }
+
+        String reason = REASON_NOT_SET;
+        PerunAttributeValue attrValue = perunAdapter.getUserAttributeValue(user.getId(), isCesnetEligibleAttrName);
+        if (attrValue != null) {
+            LocalDateTime timeStamp;
+            try {
+                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
+                timeStamp = LocalDateTime.parse(attrValue.valueAsString(), formatter);
+            } catch (DateTimeParseException e) {
+                log.warn("{} - could not parse timestamp from attribute '{}' value: '{}'",
+                        filterName, isCesnetEligibleAttrName, attrValue.valueAsString());
+                log.debug("{} - skip execution: no timestamp to compare to", filterName);
+                log.trace("{} - details:", filterName, e);
+                return true;
+            }
+
+            LocalDateTime now = LocalDateTime.now();
+            if (now.minusMonths(validityPeriod).isBefore(timeStamp)) {
+                log.debug("{} - attribute '{}' value is valid", filterName, isCesnetEligibleAttrName);
+                return true;
+            } else {
+                reason = REASON_EXPIRED;
+            }
+        }
+
+        log.debug("{} - attribute '{}' value is invalid, stop user at this point", filterName, attrValue);
+        this.redirect(request, response, reason);
+        return false;
+    }
+
+    private void redirect(HttpServletRequest req, HttpServletResponse res, String reason) {
+        Map<String, String> params = new HashMap<>();
+
+        String targetURL = FiltersUtils.buildRequestURL(req, Collections.singletonMap(PARAM_FORCE_AUTHN, "true"));
+        params.put(PARAM_TARGET, targetURL);
+        params.put(PARAM_REASON, reason);
+
+        String redirectUrl = ControllerUtils.createRedirectUrl(req, PerunFilterConstants.AUTHORIZE_REQ_PATTERN,
+                PerunUnapprovedController.UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING, params);
+        log.debug("{} - redirecting user to unapproved: URL '{}'", filterName, redirectUrl);
+        res.reset();
+        res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+        res.setHeader(HttpHeaders.LOCATION, redirectUrl);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsTestSpFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsTestSpFilter.java
new file mode 100644
index 000000000..f7af2146b
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/PerunIsTestSpFilter.java
@@ -0,0 +1,108 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_TARGET;
+import static cz.muni.ics.oidc.web.controllers.IsTestSpController.IS_TEST_SP_APPROVED_SESS;
+
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.filters.FilterParams;
+import cz.muni.ics.oidc.server.filters.FiltersUtils;
+import cz.muni.ics.oidc.server.filters.PerunFilterConstants;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilterParams;
+import cz.muni.ics.oidc.web.controllers.ControllerUtils;
+import cz.muni.ics.oidc.web.controllers.IsTestSpController;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.http.HttpHeaders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This filter forwards user to a warning page if the service is in test environment.
+ * Otherwise, user can to access the service.
+ *
+ * Configuration (replace [name] part with the name defined for the filter):
+ * <ul>
+ *     <li><b>filter.[name].isTestSpAttr</b> - mapping to isCesnetEligible attribute</li>
+ * </ul>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ * @author Pavol Pluta <500348@mail.muni.cz>
+ */
+public class PerunIsTestSpFilter extends PerunRequestFilter {
+
+    private static final Logger log = LoggerFactory.getLogger(PerunIsTestSpFilter.class);
+
+    private static final String IS_TEST_SP_ATTR_NAME = "isTestSpAttr";
+
+    private final String isTestSpAttrName;
+    private final PerunAdapter perunAdapter;
+    private final String filterName;
+
+    public PerunIsTestSpFilter(PerunRequestFilterParams params) {
+        super(params);
+        BeanUtil beanUtil = params.getBeanUtil();
+        this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
+        this.isTestSpAttrName = params.getProperty(IS_TEST_SP_ATTR_NAME);
+        this.filterName = params.getFilterName();
+    }
+
+    @Override
+    protected boolean process(ServletRequest req, ServletResponse res, FilterParams params) throws IOException {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        Facility facility = params.getFacility();
+        if (facility == null || facility.getId() == null) {
+            log.debug("{} - skip execution: no facility provided", filterName);
+            return true;
+        } else if (testSpWarningApproved(request)){
+            log.debug("{} - skip execution: warning already approved", filterName);
+            return true;
+        }
+
+        PerunAttributeValue attrValue = perunAdapter.getFacilityAttributeValue(facility.getId(), isTestSpAttrName);
+        if (attrValue == null) {
+            log.debug("{} - skip execution: attribute {} has null value", filterName, isTestSpAttrName);
+            return true;
+        } else if (attrValue.valueAsBoolean()) {
+            log.debug("{} - redirecting user to test SP warning page", filterName);
+            this.redirect(request, response);
+            return false;
+        }
+        log.debug("{} - service is not testing, let user access it", filterName);
+        return true;
+    }
+
+    private boolean testSpWarningApproved(HttpServletRequest req) {
+        if (req.getSession() == null) {
+            return false;
+        }
+        boolean approved = false;
+        if (req.getSession().getAttribute(IS_TEST_SP_APPROVED_SESS) != null) {
+            approved = (Boolean) req.getSession().getAttribute(IS_TEST_SP_APPROVED_SESS);
+            req.getSession().removeAttribute(IS_TEST_SP_APPROVED_SESS);
+        }
+        return approved;
+    }
+
+    private void redirect(HttpServletRequest req, HttpServletResponse res) {
+        String targetURL = FiltersUtils.buildRequestURL(req);
+
+        Map<String, String> params = new HashMap<>();
+        params.put(PARAM_TARGET, targetURL);
+        String redirectUrl = ControllerUtils.createRedirectUrl(req, PerunFilterConstants.AUTHORIZE_REQ_PATTERN,
+                IsTestSpController.MAPPING, params);
+        log.debug("{} - redirecting user to testSP warning page: {}", filterName, redirectUrl);
+        res.reset();
+        res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+        res.setHeader(HttpHeaders.LOCATION, redirectUrl);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ProxyStatisticsFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ProxyStatisticsFilter.java
new file mode 100644
index 000000000..984e1d18b
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ProxyStatisticsFilter.java
@@ -0,0 +1,204 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.SAML_EPUID;
+
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.server.filters.FilterParams;
+import cz.muni.ics.oidc.server.filters.FiltersUtils;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilterParams;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDate;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.sql.DataSource;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.saml.SAMLCredential;
+import org.springframework.util.StringUtils;
+
+
+/**
+ * Filter for collecting data about login.
+ *
+ * Configuration (replace [name] part with the name defined for the filter):
+ * <ul>
+ *     <li><b>filter.[name].idpNameAttributeName</b> - Mapping to Request attribute containing name of used
+ *         Identity Provider</li>
+ *     <li><b>filter.[name].idpEntityIdAttributeName</b> - Mapping to Request attribute containing entity_id of used
+ *         Identity Provider</li>
+ *     <li><b>filter.[name].statisticsTableName</b> - Name of the table where to store data
+ *         (depends on DataSource bean mitreIdStats)</li>
+ *     <li><b>filter.[name].identityProvidersMapTableName</b> - Name of the table with mapping of entity_id (IDP)
+ *         to idp name (depends on DataSource bean mitreIdStats)
+ *     <li><b>filter.[name].serviceProvidersMapTableName</b> - Name of the table with mapping of client_id (SP)
+ *         to client name (depends on DataSource bean mitreIdStats)</li>
+ * </ul>
+ *
+ * @author Dominik Baránek <baranek@ics.muni.cz>
+ */
+@SuppressWarnings("SqlResolve")
+public class ProxyStatisticsFilter extends PerunRequestFilter {
+
+	private static final Logger log = LoggerFactory.getLogger(ProxyStatisticsFilter.class);
+
+	/* CONFIGURATION OPTIONS */
+	private static final String IDP_NAME_ATTRIBUTE_NAME = "idpNameAttributeName";
+	private static final String IDP_ENTITY_ID_ATTRIBUTE_NAME = "idpEntityIdAttributeName";
+	private static final String STATISTICS_TABLE_NAME = "statisticsTableName";
+	private static final String IDENTITY_PROVIDERS_MAP_TABLE_NAME = "identityProvidersMapTableName";
+	private static final String SERVICE_PROVIDERS_MAP_TABLE_NAME = "serviceProvidersMapTableName";
+
+	private final String idpNameAttributeName;
+	private final String idpEntityIdAttributeName;
+	private final String statisticsTableName;
+	private final String identityProvidersMapTableName;
+	private final String serviceProvidersMapTableName;
+	/* END OF CONFIGURATION OPTIONS */
+
+	private final DataSource mitreIdStats;
+	private final String filterName;
+
+	public ProxyStatisticsFilter(PerunRequestFilterParams params) {
+		super(params);
+		BeanUtil beanUtil = params.getBeanUtil();
+		this.mitreIdStats = beanUtil.getBean("mitreIdStats", DataSource.class);
+
+		this.idpNameAttributeName = params.getProperty(IDP_NAME_ATTRIBUTE_NAME);
+		this.idpEntityIdAttributeName = params.getProperty(IDP_ENTITY_ID_ATTRIBUTE_NAME);
+		this.statisticsTableName = params.getProperty(STATISTICS_TABLE_NAME);
+		this.identityProvidersMapTableName = params.getProperty(IDENTITY_PROVIDERS_MAP_TABLE_NAME);
+		this.serviceProvidersMapTableName = params.getProperty(SERVICE_PROVIDERS_MAP_TABLE_NAME);
+		this.filterName = params.getFilterName();
+	}
+
+	@Override
+	protected boolean process(ServletRequest req, ServletResponse res, FilterParams params) {
+		HttpServletRequest request = (HttpServletRequest) req;
+
+		ClientDetailsEntity client = params.getClient();
+		if (client == null) {
+			log.debug("{} - skip execution: no client provided", filterName);
+			return true;
+		}
+
+		String clientIdentifier = client.getClientId();
+		String clientName = client.getClientName();
+		SAMLCredential samlCredential = FiltersUtils.getSamlCredential(request);
+
+		String idpEntityId = samlCredential.getAttributeAsString(idpEntityIdAttributeName);
+		idpEntityId = this.changeParamEncoding(idpEntityId);
+		String idpName = samlCredential.getAttributeAsString(idpNameAttributeName);
+		idpName = this.changeParamEncoding(idpName);
+		if (!StringUtils.hasText(idpEntityId) || !StringUtils.hasText(idpName)) {
+			log.debug("{} - skip execution: no source IDP provided", filterName);
+			return true;
+		}
+
+		String userId = samlCredential.getAttributeAsString(SAML_EPUID);
+		if (!StringUtils.hasText(userId)) {
+			log.debug("{} - skip execution: no user ID available", filterName);
+			return true;
+		}
+
+		this.insertLogin(idpEntityId, idpName, clientIdentifier, clientName, userId);
+		this.logUserLogin(idpEntityId, clientIdentifier, clientName, userId);
+
+		return true;
+	}
+
+	private void insertLogin(String idpEntityId, String idpName, String spIdentifier, String spName, String userId) {
+		LocalDate date = LocalDate.now();
+		String insertLoginQuery = "INSERT INTO " + statisticsTableName + "(day, idpId, spId, user, logins)" +
+				" VALUES(?, ?, ?, ?, '1') ON DUPLICATE KEY UPDATE logins = logins + 1";
+
+		try (Connection c = mitreIdStats.getConnection()) {
+			insertIdpMap(c, idpEntityId, idpName);
+			insertSpMap(c, spIdentifier, spName);
+			int idpId = extractIdpId(c, idpEntityId);
+			int spId = extractSpId(c, spIdentifier);
+
+			try (PreparedStatement preparedStatement = c.prepareStatement(insertLoginQuery)) {
+				preparedStatement.setDate(1, Date.valueOf(date));
+				preparedStatement.setInt(2, idpId);
+				preparedStatement.setInt(3, spId);
+				preparedStatement.setString(4, userId);
+				preparedStatement.execute();
+				log.trace("{} - login entry stored ({}, {}, {}, {}, {})", filterName, idpEntityId, idpName,
+						spIdentifier, spName, userId);
+			}
+		} catch (SQLException ex) {
+			log.warn("{} - caught SQLException", filterName);
+			log.debug("{} - details:", filterName, ex);
+		}
+	}
+
+	private int extractSpId(Connection c, String spIdentifier) throws SQLException {
+		String getSpIdQuery = "SELECT * FROM " + serviceProvidersMapTableName + " WHERE identifier= ?";
+
+		try (PreparedStatement preparedStatement = c.prepareStatement(getSpIdQuery)) {
+			preparedStatement.setString(1, spIdentifier);
+			ResultSet rs = preparedStatement.executeQuery();
+			rs.first();
+			return rs.getInt("spId");
+		}
+	}
+
+	private int extractIdpId(Connection c, String idpEntityId) throws SQLException {
+		String getIdPIdQuery = "SELECT * FROM " + identityProvidersMapTableName + " WHERE identifier = ?";
+
+		try (PreparedStatement preparedStatement = c.prepareStatement(getIdPIdQuery)) {
+			preparedStatement.setString(1, idpEntityId);
+			ResultSet rs = preparedStatement.executeQuery();
+			rs.first();
+			return rs.getInt("idpId");
+		}
+	}
+
+	private void insertSpMap(Connection c, String spIdentifier, String spName) throws SQLException {
+		String insertSpMapQuery = "INSERT INTO " + serviceProvidersMapTableName + "(identifier, name)" +
+				" VALUES (?, ?) ON DUPLICATE KEY UPDATE name = ?";
+
+		try (PreparedStatement preparedStatement = c.prepareStatement(insertSpMapQuery)) {
+			preparedStatement.setString(1, spIdentifier);
+			preparedStatement.setString(2, spName);
+			preparedStatement.setString(3, spName);
+			preparedStatement.execute();
+			log.trace("{} - SP map entry inserted", filterName);
+		}
+	}
+
+	private void insertIdpMap(Connection c, String idpEntityId, String idpName) throws SQLException {
+		String insertIdpMapQuery = "INSERT INTO " + identityProvidersMapTableName + "(identifier, name)" +
+				" VALUES (?, ?) ON DUPLICATE KEY UPDATE name = ?";
+
+		try (PreparedStatement preparedStatement = c.prepareStatement(insertIdpMapQuery)) {
+			preparedStatement.setString(1, idpEntityId);
+			preparedStatement.setString(2, idpName);
+			preparedStatement.setString(3, idpName);
+			preparedStatement.execute();
+			log.trace("{} - IdP map entry inserted", filterName);
+		}
+	}
+
+	private String changeParamEncoding(String original) {
+		if (original != null && !original.isEmpty()) {
+			byte[] sourceBytes = original.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1);
+			return new String(sourceBytes, java.nio.charset.StandardCharsets.UTF_8);
+		}
+
+		return null;
+	}
+
+	private void logUserLogin(String idpEntityId, String spIdentifier, String spName, String userId) {
+		log.info("User identity: {}, service: {}, serviceName: {}, via IdP: {}", userId, spIdentifier,
+				spName, idpEntityId);
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ValidUserFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ValidUserFilter.java
new file mode 100644
index 000000000..4ed4e5240
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/ValidUserFilter.java
@@ -0,0 +1,181 @@
+package cz.muni.ics.oidc.server.filters.impl;
+
+import cz.muni.ics.oidc.BeanUtil;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunUser;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig;
+import cz.muni.ics.oidc.server.filters.FilterParams;
+import cz.muni.ics.oidc.server.filters.FiltersUtils;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilter;
+import cz.muni.ics.oidc.server.filters.PerunRequestFilterParams;
+import cz.muni.ics.oidc.web.controllers.PerunUnapprovedController;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+
+/**
+ * Filter that decides if the user is valid or not. It checks for membership in specified groups and VOs. In addition,
+ * if service identifier is present and can be obtained, it also checks membership in specified groups and VOs based on
+ * the environment the service is in.
+ *
+ * Configuration (replace [name] part with the name defined for the filter):
+ * <ul>
+ *     <li><b>filter.[name].allEnvGroups</b> - Comma separated list of GROUP IDs the user must be always member of</li>
+ *     <li><b>filter.[name].allEnvGroups</b> - Comma separated list of VO IDs the user must be always member of</li>
+ *     <li><b>filter.[name].testEnvGroups</b> - Comma separated list of GROUP IDs the user must be member of if service
+ *  *         is in the test environment</li>
+ *     <li><b>filter.[name].testEnvVos</b> - Comma separated list of VO IDs the user must be member of if service
+ *  *         is in the test environment</li>
+ *     <li><b>filter.[name].prodEnvGroups</b> - Comma separated list of GROUP IDs the user must be member of if service
+ *  *         is in the production environment</li>
+ *     <li><b>filter.[name].prodEnvVos</b> - Comma separated list of VO IDs the user must be member of if service
+ *         is in the production environment</li>
+ * </ul>
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@SuppressWarnings("SqlResolve")
+public class ValidUserFilter extends PerunRequestFilter {
+
+	private static final Logger log = LoggerFactory.getLogger(ValidUserFilter.class);
+
+	/* CONFIGURATION OPTIONS */
+	private static final String ALL_ENV_GROUPS = "allEnvGroups";
+	private static final String ALL_ENV_VOS = "allEnvVos";
+	private static final String TEST_ENV_GROUPS = "testEnvGroups";
+	private static final String TEST_ENV_VOS = "testEnvVos";
+	private static final String PROD_ENV_GROUPS = "prodEnvGroups";
+	private static final String PROD_ENV_VOS = "prodEnvVos";
+
+	private final Set<Long> allEnvGroups;
+	private final Set<Long> allEnvVos;
+	private final Set<Long> testEnvGroups;
+	private final Set<Long> testEnvVos;
+	private final Set<Long> prodEnvGroups;
+	private final Set<Long> prodEnvVos;
+	/* END OF CONFIGURATION OPTIONS */
+
+	private final PerunAdapter perunAdapter;
+	private final FacilityAttrsConfig facilityAttrsConfig;
+	private final String filterName;
+
+	public ValidUserFilter(PerunRequestFilterParams params) {
+		super(params);
+		BeanUtil beanUtil = params.getBeanUtil();
+		this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
+		this.facilityAttrsConfig = beanUtil.getBean(FacilityAttrsConfig.class);
+
+		this.allEnvGroups = this.getIdsFromParam(params, ALL_ENV_GROUPS);
+		this.allEnvVos = this.getIdsFromParam(params, ALL_ENV_VOS);
+		this.testEnvGroups = this.getIdsFromParam(params, TEST_ENV_GROUPS);
+		this.testEnvVos = this.getIdsFromParam(params, TEST_ENV_VOS);
+		this.prodEnvGroups = this.getIdsFromParam(params, PROD_ENV_GROUPS);
+		this.prodEnvVos = this.getIdsFromParam(params, PROD_ENV_VOS);
+		this.filterName = params.getFilterName();
+	}
+
+	@Override
+	protected boolean process(ServletRequest req, ServletResponse res, FilterParams params) {
+		HttpServletRequest request = (HttpServletRequest) req;
+		HttpServletResponse response = (HttpServletResponse) res;
+
+		Set<Long> additionalVos = new HashSet<>();
+		Set<Long> additionalGroups = new HashSet<>();
+
+		PerunUser user = params.getUser();
+
+		if (user == null || user.getId() == null) {
+			log.debug("{} - skip filter execution: no user provided", filterName);
+			return true;
+		}
+
+		Facility facility = params.getFacility();
+		if (facility == null || facility.getId() == null) {
+			log.debug("{} - skip filter execution: no facility provided", filterName);
+			return true;
+		}
+
+		if (!checkMemberValidInGroupsAndVos(user, facility, request, response, params, allEnvVos, allEnvGroups,
+				PerunUnapprovedController.UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS)) {
+			return false;
+		}
+
+		PerunAttributeValue isTestSp = perunAdapter.getFacilityAttributeValue(facility.getId(), facilityAttrsConfig.getTestSpAttr());
+		boolean isTestSpBool = false;
+		if (isTestSp != null) {
+			isTestSpBool = isTestSp.valueAsBoolean();
+		}
+		log.debug("{} - service {} in test env", filterName, (isTestSpBool ? "is" : "is not"));
+		if (isTestSpBool) {
+			additionalVos.addAll(testEnvVos);
+			additionalGroups.addAll(testEnvGroups);
+
+			if (!checkMemberValidInGroupsAndVos(user, facility, request, response, params, additionalVos,
+					additionalGroups, PerunUnapprovedController.UNAPPROVED_NOT_IN_TEST_VOS_GROUPS)) {
+				return false;
+			}
+		} else {
+			additionalVos.addAll(prodEnvVos);
+			additionalGroups.addAll(prodEnvGroups);
+
+			if (!checkMemberValidInGroupsAndVos(user, facility, request, response, params, additionalVos,
+					additionalGroups, PerunUnapprovedController.UNAPPROVED_NOT_IN_PROD_VOS_GROUPS)) {
+				return false;
+			}
+		}
+
+		log.info("{} - user satisfies the membership criteria", filterName);
+		return true;
+	}
+
+	private Set<Long> getIdsFromParam(PerunRequestFilterParams params, String propKey) {
+		Set<Long> result = new HashSet<>();
+
+		String prop = params.getProperty(propKey);
+		if (StringUtils.hasText(prop)) {
+			String[] parts = prop.split(",");
+			for (String idStr: parts) {
+				result.add(Long.parseLong(idStr));
+			}
+		}
+
+		return result;
+	}
+
+	private boolean checkMemberValidInGroupsAndVos(
+			PerunUser user,
+			Facility facility,
+			HttpServletRequest request,
+			HttpServletResponse response,
+			FilterParams params,
+			Set<Long> vos,
+			Set<Long> groups,
+			String redirectUrl
+	) {
+		if (!perunAdapter.isValidMemberInGroupsAndVos(user.getId(), vos, groups)) {
+			log.info("{} - user is not member in required set of vos and groups", filterName);
+			log.debug("{} - user: '{}', vos: '{}', groups: '{}'",
+					filterName, user.getId(), vos, groups);
+
+			Map<String, PerunAttributeValue> facilityAttributes = perunAdapter.getFacilityAttributeValues(
+					facility, facilityAttrsConfig.getMembershipAttrNames());
+
+			FiltersUtils.redirectUserCannotAccess(request, response, facility, user, params.getClientIdentifier(),
+					facilityAttrsConfig, facilityAttributes, perunAdapter, redirectUrl);
+
+			return false;
+		}
+		return true;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/mdc/RemoteAddressMDCFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/mdc/RemoteAddressMDCFilter.java
new file mode 100644
index 000000000..541b39c0d
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/mdc/RemoteAddressMDCFilter.java
@@ -0,0 +1,43 @@
+package cz.muni.ics.oidc.server.filters.impl.mdc;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.MDC;
+
+public class RemoteAddressMDCFilter {
+
+    private static final String[] IP_HEADER_CANDIDATES = {
+            "X-Forwarded-For",
+            "Proxy-Client-IP",
+            "WL-Proxy-Client-IP",
+            "HTTP_X_FORWARDED_FOR",
+            "HTTP_X_FORWARDED",
+            "HTTP_X_CLUSTER_CLIENT_IP",
+            "HTTP_CLIENT_IP",
+            "HTTP_FORWARDED_FOR",
+            "HTTP_FORWARDED",
+            "HTTP_VIA",
+            "REMOTE_ADDR"
+    };
+
+    private static final String REMOTE_ADDR = "remoteAddr";
+
+    public void doFilter(ServletRequest servletRequest) {
+        MDC.put(REMOTE_ADDR, getRemoteAddr((HttpServletRequest) servletRequest));
+    }
+
+    private String getRemoteAddr(HttpServletRequest request) {
+        if (request.getRemoteAddr() != null) {
+            return request.getRemoteAddr();
+        }
+
+        for (String header: IP_HEADER_CANDIDATES) {
+            String ipList = request.getHeader(header);
+            if (ipList != null && ipList.length() != 0 && !"unknown".equalsIgnoreCase(ipList)) {
+                return ipList.split(",")[0];
+            }
+        }
+        return "-";
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/mdc/SessionIdMDCFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/mdc/SessionIdMDCFilter.java
new file mode 100644
index 000000000..f4ba622ed
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/filters/impl/mdc/SessionIdMDCFilter.java
@@ -0,0 +1,23 @@
+package cz.muni.ics.oidc.server.filters.impl.mdc;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.MDC;
+
+public class SessionIdMDCFilter {
+
+    private static final int SIZE = 12;
+    private static final String SESSION_ID = "sessionID";
+
+    public void doFilter(ServletRequest servletRequest) {
+        HttpServletRequest req = (HttpServletRequest) servletRequest;
+        if (req.getSession() != null) {
+            String id = req.getSession().getId();
+            if (id != null && id.length() > SIZE) {
+                id = id.substring(0, SIZE);
+            }
+            MDC.put(SESSION_ID, id);
+        }
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/PerunUserInfo.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/PerunUserInfo.java
new file mode 100644
index 000000000..2e47845c0
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/PerunUserInfo.java
@@ -0,0 +1,68 @@
+package cz.muni.ics.oidc.server.userInfo;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import cz.muni.ics.openid.connect.model.DefaultUserInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements UserInfo by inheriting from DefaultUserInfo and adding more claims.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunUserInfo extends DefaultUserInfo {
+
+	private final static Logger log = LoggerFactory.getLogger(PerunUserInfo.class);
+
+	private final Map<String, JsonNode> customClaims = new LinkedHashMap<>();
+	private JsonObject obj;
+
+	public Map<String, JsonNode> getCustomClaims() {
+		return customClaims;
+	}
+
+	@Override
+	public JsonObject toJson() {
+		if (obj == null) {
+			//delegate standard claims to DefaultUserInfo
+			obj = super.toJson();
+			//add custom claims
+			for (Map.Entry<String, JsonNode> entry : customClaims.entrySet()) {
+				String key = entry.getKey();
+				JsonNode value = entry.getValue();
+				if (value == null || value.isNull()) {
+					obj.addProperty(key, (String) null);
+					log.debug("adding null claim {}=null", key);
+				} else if (value.isTextual() || value.isBoolean()) {
+					obj.addProperty(key, value.asText());
+					log.debug("adding string claim {}={}", key, value.asText());
+				} else if (value.isNumber()) {
+					obj.addProperty(key, value.asLong());
+					log.debug("adding long claim {}={}", key, value.asText());
+				} else if (value.isContainerNode()) {
+					try {
+						//convert from Jackson to GSon
+						String rawJson = new ObjectMapper().writeValueAsString(value);
+						obj.add(key, new JsonParser().parse(rawJson));
+						log.debug("adding JSON claim {}={}", key, rawJson);
+					} catch (JsonProcessingException | JsonSyntaxException e) {
+						log.error("cannot convert Jackson/Gson value " + value, e);
+					}
+				} else {
+					log.warn("claim {} is of unknown type {}, skipping", key, value.getNodeType().toString());
+				}
+			}
+		} else {
+			log.debug("already rendered to JSON");
+		}
+		return obj;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/PerunUserInfoService.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/PerunUserInfoService.java
new file mode 100644
index 000000000..3e8f9031c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/PerunUserInfoService.java
@@ -0,0 +1,670 @@
+package cz.muni.ics.oidc.server.userInfo;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.UncheckedExecutionException;
+import cz.muni.ics.oidc.exceptions.ConfigurationException;
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.PerunAttributeValueAwareModel;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.claims.ClaimContextCommonParameters;
+import cz.muni.ics.oidc.server.claims.ClaimModifier;
+import cz.muni.ics.oidc.server.claims.ClaimModifierInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSource;
+import cz.muni.ics.oidc.server.claims.ClaimSourceInitContext;
+import cz.muni.ics.oidc.server.claims.ClaimSourceProduceContext;
+import cz.muni.ics.oidc.server.claims.PerunCustomClaimDefinition;
+import cz.muni.ics.oidc.server.claims.modifiers.NoOperationModifier;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import javax.annotation.PostConstruct;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.model.Address;
+import cz.muni.ics.openid.connect.model.DefaultAddress;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.UserInfoService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+
+/**
+ * Service called from UserInfoEndpoint and other places to get UserInfo.
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ */
+public class PerunUserInfoService implements UserInfoService {
+
+	private static final Logger log = LoggerFactory.getLogger(PerunUserInfoService.class);
+
+	private static final String CUSTOM_CLAIM = "custom.claim.";
+	private static final String SOURCE = ".source";
+	private static final String CLASS = ".class";
+	private static final String NAMES = ".names";
+	private static final String MODIFIER = ".modifier";
+
+	@Autowired
+	private ClientDetailsEntityService clientService;
+
+	@Autowired
+	private JWTSigningAndValidationService jwtService;
+
+	@Autowired
+	private PerunOidcConfig perunOidcConfig;
+
+	private List<String> forceRegenerateUserinfoCustomClaims = new ArrayList<>();
+	private List<String> forceRegenerateUserinfoStandardClaims = new ArrayList<>();
+
+	private final LoadingCache<UserClientPair, UserInfo> cache;
+
+	private PerunAdapter perunAdapter;
+	private Properties properties;
+
+	private String subAttribute;
+	private List<ClaimModifier> subModifiers;
+	private String preferredUsernameAttribute;
+	private String givenNameAttribute;
+	private String familyNameAttribute;
+	private String middleNameAttribute;
+	private String fullNameAttribute;
+	private String emailAttribute;
+	private String addressAttribute;
+	private String phoneAttribute;
+	private String zoneinfoAttribute;
+	private String localeAttribute;
+	private Set<String> customClaimNames;
+	private List<PerunCustomClaimDefinition> customClaims = new ArrayList<>();
+	private UserInfoModifierContext userInfoModifierContext;
+
+	private final Set<String> userAttrNames = new HashSet<>();
+
+	public void setProperties(Properties properties) {
+		this.properties = properties;
+	}
+
+	public void setPerunAdapter(PerunAdapter perunAdapter) {
+		this.perunAdapter = perunAdapter;
+	}
+
+	public void setSubAttribute(String subAttribute) {
+		if (this.subAttribute != null) {
+			userAttrNames.remove(this.subAttribute);
+		}
+		userAttrNames.add(subAttribute);
+		this.subAttribute = subAttribute;
+	}
+
+	public void setPreferredUsernameAttribute(String preferredUsernameAttribute) {
+		if (this.preferredUsernameAttribute != null) {
+			userAttrNames.remove(this.preferredUsernameAttribute);
+		}
+		userAttrNames.add(preferredUsernameAttribute);
+		this.preferredUsernameAttribute = preferredUsernameAttribute;
+	}
+
+	public void setGivenNameAttribute(String givenNameAttribute) {
+		if (this.givenNameAttribute != null) {
+			userAttrNames.remove(this.givenNameAttribute);
+		}
+		userAttrNames.add(givenNameAttribute);
+		this.givenNameAttribute = givenNameAttribute;
+	}
+
+	public void setFamilyNameAttribute(String familyNameAttribute) {
+		if (this.familyNameAttribute != null) {
+			userAttrNames.remove(this.familyNameAttribute);
+		}
+		userAttrNames.add(familyNameAttribute);
+		this.familyNameAttribute = familyNameAttribute;
+	}
+
+	public void setMiddleNameAttribute(String middleNameAttribute) {
+		if (this.middleNameAttribute != null) {
+			userAttrNames.remove(this.middleNameAttribute);
+		}
+		userAttrNames.add(middleNameAttribute);
+		this.middleNameAttribute = middleNameAttribute;
+	}
+
+	public void setFullNameAttribute(String fullNameAttribute) {
+		if (this.fullNameAttribute != null) {
+			userAttrNames.remove(this.fullNameAttribute);
+		}
+		userAttrNames.add(fullNameAttribute);
+		this.fullNameAttribute = fullNameAttribute;
+	}
+
+	public void setEmailAttribute(String emailAttribute) {
+		if (this.emailAttribute != null) {
+			userAttrNames.remove(this.emailAttribute);
+		}
+		userAttrNames.add(emailAttribute);
+		this.emailAttribute = emailAttribute;
+	}
+
+	public void setAddressAttribute(String addressAttribute) {
+		if (this.addressAttribute != null) {
+			userAttrNames.remove(this.addressAttribute);
+		}
+		userAttrNames.add(addressAttribute);
+		this.addressAttribute = addressAttribute;
+	}
+
+	public void setPhoneAttribute(String phoneAttribute) {
+		if (this.phoneAttribute != null) {
+			userAttrNames.remove(this.phoneAttribute);
+		}
+		userAttrNames.add(phoneAttribute);
+		this.phoneAttribute = phoneAttribute;
+	}
+
+	public void setZoneinfoAttribute(String zoneinfoAttribute) {
+		if (this.zoneinfoAttribute != null) {
+			userAttrNames.remove(this.zoneinfoAttribute);
+		}
+		userAttrNames.add(zoneinfoAttribute);
+		this.zoneinfoAttribute = zoneinfoAttribute;
+	}
+
+	public void setLocaleAttribute(String localeAttribute) {
+		if (this.localeAttribute != null) {
+			userAttrNames.remove(this.localeAttribute);
+		}
+		userAttrNames.add(localeAttribute);
+		this.localeAttribute = localeAttribute;
+	}
+
+	public void setCustomClaimNames(Set<String> customClaimNames) {
+		this.customClaimNames = customClaimNames;
+	}
+
+	public void setForceRegenerateUserinfoCustomClaims(String[] claims) {
+		this.forceRegenerateUserinfoCustomClaims = Arrays.asList(claims);
+	}
+
+	public void setForceRegenerateUserinfoStandardClaims(String[] claims) {
+		this.forceRegenerateUserinfoStandardClaims = Arrays.asList(claims);
+	}
+
+	public List<PerunCustomClaimDefinition> getCustomClaims() {
+		return customClaims;
+	}
+
+	@PostConstruct
+	public void postInit() throws ConfigurationException {
+		log.debug("trying to load modifier for attribute.openid.sub");
+		subModifiers = loadClaimValueModifiers("sub", "attribute.openid.sub" + MODIFIER);
+		//custom claims
+		this.customClaims = new ArrayList<>(customClaimNames.size());
+		for (String claimName : customClaimNames) {
+			String propertyBase = CUSTOM_CLAIM + claimName;
+			//get scope
+			String scopeProperty = propertyBase + ".scope";
+			String scope = properties.getProperty(scopeProperty);
+			if (scope == null) {
+				log.error("property {} not found, skipping custom claim {}", scopeProperty, claimName);
+				continue;
+			}
+			//get ClaimSource
+			ClaimSource claimSource = loadClaimSource(claimName, propertyBase + SOURCE);
+			userAttrNames.addAll(claimSource.getAttrIdentifiers());
+			//optional claim value modifier
+			List<ClaimModifier> claimModifiers = loadClaimValueModifiers(claimName, propertyBase + MODIFIER);
+			//add claim definition
+			customClaims.add(new PerunCustomClaimDefinition(scope, claimName, claimSource, claimModifiers));
+
+		}
+
+		this.userInfoModifierContext = new UserInfoModifierContext(properties, perunAdapter);
+	}
+
+	@Override
+	public UserInfo getByUsernameAndClientId(String username, String clientId) {
+		ClientDetailsEntity client = clientService.loadClientByClientId(clientId);
+		if (client == null) {
+			log.warn("did not found client with id {}", clientId);
+			return null;
+		}
+
+		PerunUserInfo userInfo;
+		try {
+			UserClientPair cacheKey = new UserClientPair(username, clientId, client);
+			userInfo = (PerunUserInfo) cache.get(cacheKey);
+			if (!checkStandardClaims(userInfo) || !checkCustomClaims(userInfo)) {
+				log.info("Some required claim is null, regenerate userInfo");
+				cache.invalidate(cacheKey);
+				userInfo = (PerunUserInfo) cache.get(cacheKey);
+			}
+			log.debug("loaded UserInfo from cache for '{}'/'{}'", userInfo.getName(), client.getClientName());
+			userInfo = userInfoModifierContext.modify(userInfo, clientId);
+		} catch (ExecutionException e) {
+			log.error("cannot get user from cache", e);
+			return null;
+		}
+
+		return userInfo;
+	}
+
+	@Override
+	public UserInfo getByUsername(String username) {
+		PerunUserInfo userInfo;
+		try {
+			UserClientPair cacheKey = new UserClientPair(username);
+			userInfo = (PerunUserInfo) cache.get(cacheKey);
+			if (!checkStandardClaims(userInfo) || !checkCustomClaims(userInfo)) {
+				log.info("Some required claim is null, regenerate userInfo");
+				cache.invalidate(cacheKey);
+				userInfo = (PerunUserInfo) cache.get(cacheKey);
+			}
+			log.debug("loaded UserInfo from cache for '{}'", userInfo.getName());
+			userInfo = userInfoModifierContext.modify(userInfo, null);
+		} catch (UncheckedExecutionException | ExecutionException e) {
+			log.error("cannot get user from cache", e);
+			return null;
+		}
+
+		return userInfo;
+	}
+
+	@Override
+	public UserInfo getByEmailAddress(String email) {
+		throw new RuntimeException("PerunUserInfoService.getByEmailAddress() not implemented");
+	}
+
+	public PerunUserInfoService() {
+		this.cache = CacheBuilder.newBuilder()
+				.maximumSize(100)
+				.expireAfterAccess(60, TimeUnit.SECONDS)
+				.build(cacheLoader);
+	}
+
+	private List<ClaimModifier> loadClaimValueModifiers(String claimName, String propertyPrefix)
+			throws ConfigurationException
+	{
+		String names = properties.getProperty(propertyPrefix + NAMES, "");
+		String[] nameArr = names.split(",");
+		List<ClaimModifier> modifiers = new ArrayList<>();
+		if (nameArr.length > 0) {
+			for (String name : nameArr) {
+				modifiers.add(loadClaimValueModifier(claimName, propertyPrefix + '.' + name, name));
+			}
+		}
+		return modifiers;
+	}
+
+	private ClaimModifier loadClaimValueModifier(String claimName, String propertyPrefix, String modifierName)
+			throws ConfigurationException
+	{
+		String modifierClass = properties.getProperty(propertyPrefix + CLASS, NoOperationModifier.class.getName());
+		if (!StringUtils.hasText(modifierClass)) {
+			log.debug("{}:{} - no class has ben configured for claim value modifier, use noop modifier",
+					claimName, modifierName);
+			modifierClass = NoOperationModifier.class.getName();
+		}
+		log.trace("{}:{} - loading ClaimModifier class '{}'", claimName, modifierName, modifierClass);
+
+		try {
+			Class<?> rawClazz = Class.forName(modifierClass);
+			if (!ClaimModifier.class.isAssignableFrom(rawClazz)) {
+				log.error("{}:{} - failed to initialized claim modifier: class '{}' does not extend ClaimModifier",
+						claimName, modifierName, modifierClass);
+				throw new ConfigurationException("No instantiable class modifier configured for claim " + claimName);
+			}
+			@SuppressWarnings("unchecked") Class<ClaimModifier> clazz = (Class<ClaimModifier>) rawClazz;
+			Constructor<ClaimModifier> constructor = clazz.getConstructor(ClaimModifierInitContext.class);
+			ClaimModifierInitContext ctx = new ClaimModifierInitContext(
+					propertyPrefix, properties, claimName, modifierName);
+			return constructor.newInstance(ctx);
+		} catch (ClassNotFoundException e) {
+			log.error("{}:{} - failed to initialize claim modifier: class '{}' was not found",
+					claimName, modifierName, modifierClass);
+			log.trace("{}:{} - details:", claimName, modifierName, e);
+			throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
+					+ modifierName + "' of claim '" + claimName + '\'');
+		} catch (NoSuchMethodException e) {
+			log.error("{}:{} - failed to initialize claim modifier: class '{}' does not have proper constructor",
+					claimName, modifierName, modifierClass);
+			log.trace("{}:{} - details:", claimName, e);
+			throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
+					+ modifierName + "' of claim '" + claimName + '\'');
+		} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+			log.error("{}:{} - failed to initialize claim modifier: class '{}' cannot be instantiated",
+					claimName, modifierName, modifierClass);
+			log.trace("{}:{} - details:", claimName, e);
+			throw new ConfigurationException("Error has occurred when instantiating claim modifier '"
+					+ modifierName + "' of claim '" + claimName + '\'');
+		}
+	}
+
+	private ClaimSource loadClaimSource(String claimName, String propertyPrefix) throws ConfigurationException {
+		String sourceClass = properties.getProperty(propertyPrefix + CLASS);
+		if (!StringUtils.hasText(sourceClass)) {
+			log.error("{} - failed to initialized claim source: no class has ben configured", claimName);
+			throw new ConfigurationException("No class configured for claim source");
+		}
+
+		log.trace("{} - loading ClaimSource class '{}'", claimName, sourceClass);
+
+		try {
+			Class<?> rawClazz = Class.forName(sourceClass);
+			if (!ClaimSource.class.isAssignableFrom(rawClazz)) {
+				log.error("{} - failed to initialized claim source: class '{}' does not extend ClaimSource",
+						claimName, sourceClass);
+				throw new ConfigurationException("No instantiable class source configured for claim " + claimName);
+			}
+			@SuppressWarnings("unchecked") Class<ClaimSource> clazz = (Class<ClaimSource>) rawClazz;
+			Constructor<ClaimSource> constructor = clazz.getConstructor(ClaimSourceInitContext.class);
+			ClaimSourceInitContext ctx = new ClaimSourceInitContext(perunOidcConfig, jwtService, propertyPrefix,
+					properties, claimName);
+			return constructor.newInstance(ctx);
+		} catch (ClassNotFoundException e) {
+			log.error("{} - failed to initialize claim source: class '{}' was not found", claimName, sourceClass);
+			log.trace("{} - details:", claimName, e);
+			throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
+		} catch (NoSuchMethodException e) {
+			log.error("{} - failed to initialize claim source: class '{}' does not have proper constructor",
+					claimName, sourceClass);
+			log.trace("{} - details:", claimName, e);
+			throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
+		} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+			log.error("{} - failed to initialize claim source: class '{}' cannot be instantiated", claimName, sourceClass);
+			log.trace("{} - details:", claimName, e);
+			throw new ConfigurationException("Error has occurred when instantiating claim source for claim " + claimName);
+		}
+	}
+
+	private static class UserClientPair {
+		private final long userId;
+		private String clientId;
+		private ClientDetailsEntity client;
+
+		UserClientPair(String userId) {
+			this.userId = Long.parseLong(userId);
+		}
+
+		UserClientPair(String userId, String clientId, ClientDetailsEntity client) {
+			this.userId = Long.parseLong(userId);
+			this.clientId = clientId;
+			this.client = client;
+		}
+
+		public long getUserId() {
+			return userId;
+		}
+
+		public String getClientId() {
+			return clientId;
+		}
+
+		public ClientDetailsEntity getClient() {
+			return client;
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (this == o) return true;
+			if (o == null || getClass() != o.getClass()) return false;
+			UserClientPair that = (UserClientPair) o;
+			return userId == that.userId &&
+					Objects.equals(clientId, that.clientId);
+		}
+
+		@Override
+		public int hashCode() {
+			return Objects.hash(userId, clientId);
+		}
+
+		@Override
+		public String toString() {
+			return "(" + "userId=" + userId + "," + (client == null ? "null" : "client=" + clientId + " '" + client.getClientName()) + "')";
+		}
+	}
+
+	@SuppressWarnings("FieldCanBeLocal")
+	private final CacheLoader<UserClientPair, UserInfo> cacheLoader = new CacheLoader<UserClientPair, UserInfo>() {
+		@Override
+		public UserInfo load(UserClientPair pair) {
+			log.debug("load({}) ... populating cache for the key", pair);
+			PerunUserInfo ui = new PerunUserInfo();
+			long perunUserId = pair.getUserId();
+
+			Map<String, PerunAttributeValue> userAttributeValues = perunAdapter.getUserAttributeValues(perunUserId, userAttrNames);
+
+			if (shouldFillAttrs(userAttributeValues)) {
+				List<String> attrNames = userAttributeValues.entrySet()
+						.stream()
+						.filter(entry -> (null == entry.getValue() || entry.getValue().isNullValue()))
+						.map(Map.Entry::getKey)
+						.collect(Collectors.toList());
+				Map<String, PerunAttributeValue> missingAttrs = perunAdapter.getAdapterFallback()
+						.getUserAttributeValues(perunUserId, attrNames);
+				for (Map.Entry<String, PerunAttributeValue> entry : missingAttrs.entrySet()) {
+					userAttributeValues.put(entry.getKey(), entry.getValue());
+				}
+			}
+
+			JsonNode subJson = userAttributeValues.get(subAttribute).valueAsJson();
+			if (subJson == null || subJson.isNull() || !StringUtils.hasText(subJson.asText())) {
+				throw new RuntimeException("cannot get sub from attribute " + subAttribute + " for username " + perunUserId);
+			}
+			String sub = subJson.asText();
+			if (subModifiers != null) {
+				subJson = modifyClaims(subModifiers, subJson);
+				sub = subJson.asText();
+				if (sub == null || !StringUtils.hasText(sub)) {
+					throw new RuntimeException("Sub has no value after modification for username " + perunUserId);
+				}
+			}
+
+			ui.setId(perunUserId);
+			ui.setSub(sub); // Subject - Identifier for the End-User at the Issuer.
+
+			ui.setPreferredUsername(userAttributeValues.get(preferredUsernameAttribute).valueAsString()); // Shorthand name by which the End-User wishes to be referred to at the RP
+			ui.setGivenName(userAttributeValues.get(givenNameAttribute).valueAsString()); //  Given name(s) or first name(s) of the End-User
+			ui.setFamilyName(userAttributeValues.get(familyNameAttribute).valueAsString()); // Surname(s) or last name(s) of the End-User
+			ui.setMiddleName(userAttributeValues.get(middleNameAttribute).valueAsString()); //  Middle name(s) of the End-User
+			ui.setName(userAttributeValues.get(fullNameAttribute).valueAsString()); // End-User's full name
+			//ui.setNickname(); // Casual name of the End-User
+			//ui.setProfile(); //  URL of the End-User's profile page.
+			//ui.setPicture(); // URL of the End-User's profile picture.
+			//ui.setWebsite(); // URL of the End-User's Web page or blog.
+			ui.setEmail(userAttributeValues.get(emailAttribute).valueAsString()); // End-User's preferred e-mail address.
+			//ui.setEmailVerified(true); // True if the End-User's e-mail address has been verified
+			//ui.setGender("male"); // End-User's gender. Values defined by this specification are female and male.
+			//ui.setBirthdate("1975-01-01");//End-User's birthday, represented as an ISO 8601:2004 [ISO8601‑2004] YYYY-MM-DD format.
+			ui.setZoneinfo(userAttributeValues.get(zoneinfoAttribute).valueAsString());//String from zoneinfo [zoneinfo] time zone database, For example, Europe/Paris
+			ui.setLocale(userAttributeValues.get(localeAttribute).valueAsString()); //  For example, en-US or fr-CA.
+			ui.setPhoneNumber(userAttributeValues.get(phoneAttribute).valueAsString()); //[E.164] is RECOMMENDED as the format, for example, +1 (425) 555-121
+			//ui.setPhoneNumberVerified(true); // True if the End-User's phone number has been verified
+			//ui.setUpdatedTime(Long.toString(System.currentTimeMillis()/1000L));// value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in UTC until the date/time
+			Address address = null;
+			if (StringUtils.hasText(userAttributeValues.get(addressAttribute).valueAsString())) {
+				address = new DefaultAddress();
+				address.setFormatted(userAttributeValues.get(addressAttribute).valueAsString());
+				//address.setStreetAddress("Šumavská 15");
+				//address.setLocality("Brno");
+				//address.setPostalCode("61200");
+				//address.setCountry("Czech Republic");
+			}
+			ui.setAddress(address);
+			//custom claims
+			ClaimContextCommonParameters contextCommonParameters = getClaimContextCommonParameters(perunUserId,
+					pair.getClientId(), perunAdapter);
+			ClaimSourceProduceContext pctx = new ClaimSourceProduceContext(perunUserId, sub, userAttributeValues,
+					perunAdapter, pair.getClient(), contextCommonParameters);
+			log.debug("processing custom claims");
+			for (PerunCustomClaimDefinition pccd : customClaims) {
+				log.debug("producing value for custom claim {}", pccd.getClaim());
+				JsonNode claimInJson = pccd.getClaimSource().produceValue(pctx);
+				log.debug("produced value {}={}", pccd.getClaim(), claimInJson);
+				if (claimInJson == null || claimInJson.isNull()) {
+					log.debug("claim {} is null", pccd.getClaim());
+					continue;
+				} else if (claimInJson.isTextual() && !StringUtils.hasText(claimInJson.asText())) {
+					log.debug("claim {} is a string and it is empty or null", pccd.getClaim());
+					continue;
+				} else if ((claimInJson.isArray() || claimInJson.isObject()) && claimInJson.size() == 0) {
+					log.debug("claim {} is an object or array and it is empty or null", pccd.getClaim());
+					continue;
+				}
+				List<ClaimModifier> claimModifiers = pccd.getClaimModifiers();
+				if (claimModifiers != null && !claimModifiers.isEmpty()) {
+					claimInJson = modifyClaims(claimModifiers, claimInJson);
+				}
+				ui.getCustomClaims().put(pccd.getClaim(), claimInJson);
+			}
+			log.debug("UserInfo created");
+			return ui;
+		}
+
+		private ClaimContextCommonParameters getClaimContextCommonParameters(long perunUserId, String clientId,
+																			 PerunAdapter perunAdapter)
+		{
+			Facility facility = perunAdapter.getFacilityByClientId(clientId);
+			return new ClaimContextCommonParameters(facility);
+		}
+	};
+
+	private JsonNode modifyClaims(List<ClaimModifier> claimModifiers, JsonNode value) {
+		for (ClaimModifier modifier: claimModifiers) {
+			value = modifyClaim(modifier, value);
+		}
+		return value;
+	}
+
+	private JsonNode modifyClaim(ClaimModifier modifier, JsonNode orig) {
+		JsonNode claimInJson = orig.deepCopy();
+		if (claimInJson.isTextual()) {
+			return TextNode.valueOf(modifier.modify(claimInJson.asText()));
+		} else if (claimInJson.isArray()) {
+			ArrayNode arrayNode = (ArrayNode) claimInJson;
+			for (int i = 0; i < arrayNode.size(); i++) {
+				JsonNode item = arrayNode.get(i);
+				if (item.isTextual()) {
+					String original = item.asText();
+					String modified = modifier.modify(original);
+					arrayNode.set(i, TextNode.valueOf(modified));
+				}
+			}
+			return arrayNode;
+		} else {
+			log.warn("Original value is neither string nor array of strings - cannot modify values");
+			return orig;
+		}
+	}
+
+	private boolean shouldFillAttrs(Map<String, PerunAttributeValue> userAttributeValues) {
+		if (perunOidcConfig.isFillMissingUserAttrs()) {
+			if (userAttributeValues.isEmpty()) {
+				return true;
+			} else if (userAttributeValues.containsValue(null)) {
+				return true;
+			} else {
+				return !userAttributeValues.values().stream()
+						.filter(PerunAttributeValueAwareModel::isNullValue)
+						.collect(Collectors.toSet())
+						.isEmpty();
+			}
+		}
+		return false;
+	}
+
+	private boolean checkStandardClaims(PerunUserInfo userInfo) {
+		for (String claim: forceRegenerateUserinfoStandardClaims) {
+			switch (claim.toLowerCase()) {
+				case "sub": {
+					if (userInfo.getSub() == null) {
+						return false;
+					}
+				} break;
+				case "preferred_username": {
+					if (userInfo.getPreferredUsername() == null) {
+						return false;
+					}
+				} break;
+				case "given_name": {
+					if (userInfo.getGivenName() == null) {
+						return false;
+					}
+				} break;
+				case "family_name": {
+					if (userInfo.getFamilyName() == null) {
+						return false;
+					}
+				} break;
+				case "middle_name": {
+					if (userInfo.getMiddleName() == null) {
+						return false;
+					}
+				} break;
+				case "name": {
+					if (userInfo.getName() == null) {
+						return false;
+					}
+				} break;
+				case "email": {
+					if (userInfo.getEmail() == null) {
+						return false;
+					}
+				} break;
+				case "address_formatted": {
+					if (userInfo.getAddress() == null
+							|| userInfo.getAddress().getFormatted() == null)
+					{
+						return false;
+					}
+				} break;
+				case "phone": {
+					if (userInfo.getPhoneNumber() == null) {
+						return false;
+					}
+				} break;
+				case "zoneinfo": {
+					if (userInfo.getZoneinfo() == null) {
+						return false;
+					}
+				} break;
+				case "locale": {
+					if (userInfo.getLocale() == null) {
+						return false;
+					}
+				} break;
+			}
+		}
+
+		log.debug("All required standard claims are OK");
+		return true;
+	}
+
+	private boolean checkCustomClaims(PerunUserInfo userInfo) {
+		for (String claim: forceRegenerateUserinfoCustomClaims) {
+			if (userInfo.getCustomClaims().get(claim) == null ||
+					userInfo.getCustomClaims().get(claim).isNull()) {
+				return false;
+			}
+		}
+
+		log.debug("All required custom claims are OK");
+		return true;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifier.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifier.java
new file mode 100644
index 000000000..ffb683450
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifier.java
@@ -0,0 +1,32 @@
+package cz.muni.ics.oidc.server.userInfo;
+
+/**
+ * Interface for all code that needs to modify user info.
+ *
+ * Configuration of userInfo modifiers:
+ * <ul>
+ *     <li><b>userInfo.modifiers</b> - comma separated list of names of the userInfo modifiers</li>
+ * </ul>
+ *
+ * Configuration of modifier (replace [name] part with the name defined for the modifier):
+ * <ul>
+ *     <li><b>userInfo.modifier.[name].class</b> - class the modifier instantiates</li>
+ * </ul>
+ *
+ * @see cz.muni.ics.oidc.server.userInfo.modifiers package for specific modifiers and their configuration
+ *
+ * @author Dominik Baránek <baranek@ics.muni.cz>
+ */
+public interface UserInfoModifier {
+
+	/**
+	 * Performs modification of UserInfo object. Modification depends on implementation.
+	 * ATTENTION: param clientId can be NULL. In that case, implementation should not fail, modification should be
+	 * rather skipped.
+	 *
+	 * @param perunUserInfo UserInfo to be modified
+	 * @param clientId Id of client. Can be null.
+	 */
+	void modify(PerunUserInfo perunUserInfo, String clientId);
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifierContext.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifierContext.java
new file mode 100644
index 000000000..0db8fc579
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifierContext.java
@@ -0,0 +1,83 @@
+package cz.muni.ics.oidc.server.userInfo;
+
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Context for UserInfoModifiers.
+ *
+ * @author Dominik Baránek <baranek@ics.muni.cz>
+ */
+public class UserInfoModifierContext {
+
+	private static final Logger log = LoggerFactory.getLogger(PerunUserInfoService.class);
+
+	private static final String MODIFIER_CLASS = ".class";
+
+	private final Properties properties;
+	private final PerunAdapter perunAdapter;
+	private final List<UserInfoModifier> modifiers;
+
+	public UserInfoModifierContext(Properties properties, PerunAdapter perunAdapter) {
+		this.properties = properties;
+		this.perunAdapter = perunAdapter;
+		this.modifiers = new LinkedList<>();
+
+		String modifierNamesProperty = properties.getProperty("userInfo.modifiers");
+		String[] modifierNames = modifierNamesProperty.split(",");
+		for (String m : modifierNames) {
+			UserInfoModifier modifier = loadModifier("userInfo.modifier." + m);
+			if (modifier != null) {
+				log.debug("Executing modifier {}", m);
+				modifiers.add(modifier);
+			}
+		}
+
+	}
+
+	public PerunUserInfo modify(PerunUserInfo perunUserInfo, String clientId) {
+		for (UserInfoModifier m : modifiers) {
+			m.modify(perunUserInfo, clientId);
+		}
+
+		return perunUserInfo;
+	}
+
+	private UserInfoModifier loadModifier(String propertyPrefix) {
+
+		String modifierClass = properties.getProperty(propertyPrefix + MODIFIER_CLASS, null);
+		if (modifierClass == null) {
+			return null;
+		}
+		try {
+			Class<?> rawClazz = Class.forName(modifierClass);
+			if (!UserInfoModifier.class.isAssignableFrom(rawClazz)) {
+				log.error("modifier class {} does not extend UserInfoModifier", modifierClass);
+				return null;
+			}
+			@SuppressWarnings("unchecked") Class<UserInfoModifier> clazz = (Class<UserInfoModifier>) rawClazz;
+			Constructor<UserInfoModifier> constructor = clazz.getConstructor(UserInfoModifierInitContext.class);
+			UserInfoModifierInitContext ctx = new UserInfoModifierInitContext(propertyPrefix, properties, perunAdapter);
+			UserInfoModifier userInfoModifier = constructor.newInstance(ctx);
+			log.info("loaded a modifier '{}' for {}", userInfoModifier, propertyPrefix);
+			return userInfoModifier;
+		} catch (ClassNotFoundException e) {
+			log.error("modifier class {} not found", modifierClass);
+			return null;
+		} catch (NoSuchMethodException e) {
+			log.error("modifier class {} does not have proper constructor", modifierClass);
+			return null;
+		} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+			log.error("cannot instantiate " + modifierClass, e);
+			log.error("modifier class {} cannot be instantiated", modifierClass);
+			return null;
+		}
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifierInitContext.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifierInitContext.java
new file mode 100644
index 000000000..8ddf2e99c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/server/userInfo/UserInfoModifierInitContext.java
@@ -0,0 +1,36 @@
+package cz.muni.ics.oidc.server.userInfo;
+
+
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import java.util.Properties;
+
+/**
+ * Context for initializing UserInfoModifiers.
+ *
+ * @author Dominik Baránek <baranek@ics.muni.cz>
+ */
+public class UserInfoModifierInitContext {
+
+	private final String propertyPrefix;
+	private final Properties properties;
+	private PerunAdapter perunAdapter;
+
+	public UserInfoModifierInitContext(String propertyPrefix, Properties properties, PerunAdapter perunAdapter) {
+		this.propertyPrefix = propertyPrefix;
+		this.properties = properties;
+		this.perunAdapter = perunAdapter;
+	}
+
+	public String getProperty(String suffix, String defaultValue) {
+		return properties.getProperty(propertyPrefix + "." + suffix, defaultValue);
+	}
+
+	public PerunAdapter getPerunAdapter() {
+		return perunAdapter;
+	}
+
+	public void setPerunAdapter(PerunAdapter perunAdapter) {
+		this.perunAdapter = perunAdapter;
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/WebHtmlClasses.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/WebHtmlClasses.java
new file mode 100644
index 000000000..90c02c159
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/WebHtmlClasses.java
@@ -0,0 +1,65 @@
+package cz.muni.ics.oidc.web;
+
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Static utility class for HTML pages. Contains properties that can be rendered as element classes in HTML.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class WebHtmlClasses {
+
+	private static final Logger log = LoggerFactory.getLogger(WebHtmlClasses.class);
+
+	private String classesFilePath;
+	private Properties webHtmlClassesProperties;
+
+	public WebHtmlClasses(PerunOidcConfig perunOidcConfig) {
+		this.classesFilePath = perunOidcConfig.getWebClassesFilePath();
+		initFile();
+	}
+
+	public String getClassesFilePath() {
+		return classesFilePath;
+	}
+
+	public Properties getWebHtmlClassesProperties() {
+		return webHtmlClassesProperties;
+	}
+
+	private void initFile() {
+		Properties webHtmlClassesProps = new Properties();
+		String resourceFileName = "web_classes/web_html_classes.properties";
+		try (InputStreamReader isr = new InputStreamReader(Objects.requireNonNull(
+				getClass().getClassLoader().getResourceAsStream(resourceFileName)),StandardCharsets.UTF_8)) {
+			webHtmlClassesProps.load(isr);
+			log.debug("Loaded web html classes file: {}", resourceFileName);
+		} catch (IOException e) {
+			log.warn("Exception caught when reading {}", resourceFileName, e);
+		}
+
+		String customFileName = classesFilePath;
+		try (InputStreamReader isr = new InputStreamReader(
+				new FileInputStream(customFileName), StandardCharsets.UTF_8
+		)) {
+			webHtmlClassesProps.load(isr);
+			log.debug("Loaded web html classes file: {}", customFileName);
+		} catch (FileNotFoundException e) {
+			log.warn("File: {} not found", customFileName);
+			e.printStackTrace();
+		} catch (IOException e) {
+			log.warn("Exception caught when reading {}", customFileName, e);
+		}
+
+		this.webHtmlClassesProperties = webHtmlClassesProps;
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ApproveDeviceController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ApproveDeviceController.java
new file mode 100644
index 000000000..7d86b5973
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ApproveDeviceController.java
@@ -0,0 +1,219 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import cz.muni.ics.oidc.server.PerunDeviceCodeAcrRepository;
+import cz.muni.ics.oidc.server.PerunScopeClaimTranslationService;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.server.filters.PerunFilterConstants;
+import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.security.Principal;
+import java.time.Instant;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.DeviceCode;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.oauth2.web.DeviceEndpoint;
+import cz.muni.ics.openid.connect.models.DeviceCodeAcr;
+import cz.muni.ics.openid.connect.service.UserInfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Controller
+public class ApproveDeviceController {
+
+    public static final String DEVICE = "device";
+    public static final String APPROVE_DEVICE = "approveDevice";
+    public static final String DEVICE_APPROVED = "deviceApproved";
+    public static final String REQUEST_USER_CODE = "requestUserCode";
+    public static final String USER_CODE = "user_code";
+    public static final String DEVICE_CODE = "device_code";
+    public static final String USER_OAUTH_APPROVAL = "user_oauth_approval";
+    public static final String URL = "devicecode";
+    public static final String VERIFICATION_URI = "verification_uri";
+    public static final String VERIFICATION_URI_COMPLETE = "verification_uri_complete";
+    public static final String ACR_VALUES = "acr_values";
+    public static final String ENTITY = "entity";
+    public static final String CLIENT_ID = "client_id";
+    public static final String SCOPE = "scope";
+    public static final String ACR = "acr";
+
+    private final SystemScopeService scopeService;
+    private final DeviceEndpoint deviceEndpoint;
+    private final PerunOidcConfig perunOidcConfig;
+    private final Localization localization;
+    private final WebHtmlClasses htmlClasses;
+    private final PerunScopeClaimTranslationService scopeClaimTranslationService;
+    private final UserInfoService userInfoService;
+    private final PerunDeviceCodeAcrRepository deviceCodeAcrRepository;
+
+    @Autowired
+    public ApproveDeviceController(SystemScopeService scopeService,
+                                   DeviceEndpoint deviceEndpoint,
+                                   PerunOidcConfig perunOidcConfig,
+                                   Localization localization,
+                                   WebHtmlClasses htmlClasses,
+                                   PerunScopeClaimTranslationService scopeClaimTranslationService,
+                                   UserInfoService userInfoService,
+                                   PerunDeviceCodeAcrRepository perunDeviceCodeAcrRepository)
+    {
+        this.scopeService = scopeService;
+        this.deviceEndpoint = deviceEndpoint;
+        this.perunOidcConfig = perunOidcConfig;
+        this.localization = localization;
+        this.htmlClasses = htmlClasses;
+        this.scopeClaimTranslationService = scopeClaimTranslationService;
+        this.userInfoService = userInfoService;
+        this.deviceCodeAcrRepository = perunDeviceCodeAcrRepository;
+    }
+
+    @RequestMapping(
+            value = {"/" + URL},
+            method = RequestMethod.POST,
+            consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
+            produces = {MediaType.APPLICATION_JSON_VALUE},
+            params = {CLIENT_ID, ACR_VALUES}
+    )
+    public String requestDeviceCodeMFA(@RequestParam(CLIENT_ID) String clientId, @RequestParam(name = SCOPE, required = false) String scope,
+                                       @RequestParam(name = ACR_VALUES) String acrValues, Map<String, String> parameters, ModelMap model)
+    {
+        String result = deviceEndpoint.requestDeviceCode(clientId, scope, parameters, model);
+
+        Map<String, Object> response = (Map<String, Object>) model.get(ENTITY);
+        response.replace(VERIFICATION_URI, response.get(VERIFICATION_URI) + "?" + ACR_VALUES + "=" + acrValues);
+        response.replace(VERIFICATION_URI_COMPLETE, response.get(VERIFICATION_URI_COMPLETE) + "&" + ACR_VALUES + "=" + acrValues);
+        storeAcrBase((String) response.get(DEVICE_CODE), (String)response.get(USER_CODE));
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('ROLE_USER')")
+    @GetMapping(value = "/" + DEVICE,
+            consumes = {"text/html", "application/xhtml+xml","application/xml;q=0.9","image/webp","*/*;q=0.8"})
+    public String requestUserCode(@RequestParam(value = USER_CODE, required = false) String userCode,
+                                  @ModelAttribute("authorizationRequest") AuthorizationRequest authRequest,
+                                  Principal p,
+                                  HttpServletRequest req,
+                                  ModelMap model,
+                                  HttpSession session)
+    {
+        String result = deviceEndpoint.requestUserCode(userCode, model, session);
+        if (result.equals(REQUEST_USER_CODE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
+            ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+            model.put("page", REQUEST_USER_CODE);
+            String shibAuthnContextClass = "";
+            if (StringUtils.hasText(req.getParameter(ACR_VALUES))) {
+                shibAuthnContextClass = (String) req.getAttribute(PerunFilterConstants.SHIB_AUTHN_CONTEXT_CLASS);
+                if (!StringUtils.hasText(shibAuthnContextClass)) {
+                    shibAuthnContextClass = (String) req.getAttribute(PerunFilterConstants.SHIB_AUTHN_CONTEXT_METHOD);
+                }
+                if (!StringUtils.hasText(shibAuthnContextClass)) {
+                    shibAuthnContextClass = "";
+                }
+            }
+            model.put(ACR, shibAuthnContextClass);
+            return "themedRequestUserCode";
+        } else if (result.equals(APPROVE_DEVICE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
+            return themedApproveDevice(model, p, req);
+        }
+        return result;
+    }
+
+    @PreAuthorize("hasRole('ROLE_USER')")
+    @PostMapping(value = "/" + DEVICE + "/verify",
+            consumes = {"text/html", "application/xhtml+xml","application/xml;q=0.9","image/webp","*/*;q=0.8"})
+    public String readUserCode(@RequestParam(USER_CODE) String userCode,
+                               @ModelAttribute("authorizationRequest") AuthorizationRequest authRequest,
+                               Principal p,
+                               HttpServletRequest req,
+                               ModelMap model,
+                               HttpSession session)
+    {
+        String result = deviceEndpoint.readUserCode(userCode, model, session);
+        if (result.equals(APPROVE_DEVICE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
+            if (StringUtils.hasText(req.getParameter(ACR))) {
+                storeAcr(req.getParameter(ACR), userCode);
+            }
+
+            return themedApproveDevice(model, p, req);
+        } else if (result.equals(REQUEST_USER_CODE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
+            ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+            model.put("page", REQUEST_USER_CODE);
+            return "themedRequestUserCode";
+        }
+
+        return result;
+    }
+
+    @PreAuthorize("hasRole('ROLE_USER')")
+    @PostMapping(value = "/" + DEVICE + "/approve", params = {USER_CODE, USER_OAUTH_APPROVAL})
+    public String approveDevice(@RequestParam(USER_CODE) String userCode,
+                                @RequestParam(USER_OAUTH_APPROVAL) Boolean approve,
+                                @ModelAttribute(USER_OAUTH_APPROVAL) AuthorizationRequest authRequest,
+                                Principal p,
+                                HttpServletRequest req,
+                                ModelMap model,
+                                Authentication auth,
+                                HttpSession session)
+    {
+        String result = deviceEndpoint.approveDevice(userCode, approve, model, auth, session);
+        if (result.equals(DEVICE_APPROVED) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
+            model.remove("scopes");
+
+            DeviceCode dc = (DeviceCode)session.getAttribute("deviceCode");
+            ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
+            PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
+                    p.getName(), client.getClientId());
+
+            ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, dc.getScope(), user);
+            ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+
+            model.put("page", DEVICE_APPROVED);
+            return "themedDeviceApproved";
+        }
+
+        return result;
+    }
+
+    private void storeAcr(String acrValue, String userCode) {
+        DeviceCodeAcr acr = deviceCodeAcrRepository.getByUserCode(userCode);
+        acr.setShibAuthnContextClass(acrValue);
+        long expiresAtEpoch = Instant.now().plusSeconds(600L).toEpochMilli();
+        acr.setExpiresAt(expiresAtEpoch);
+        deviceCodeAcrRepository.store(acr);
+    }
+
+    private String themedApproveDevice(ModelMap model, Principal p, HttpServletRequest req) {
+        model.remove("scopes");
+        DeviceCode dc = (DeviceCode) model.get("dc");
+        ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
+        PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
+                p.getName(), client.getClientId());
+        ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, dc.getScope(), user);
+        ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+
+        model.put("page", APPROVE_DEVICE);
+        return "themedApproveDevice";
+    }
+
+    private void storeAcrBase(String deviceCode, String userCode) {
+        DeviceCodeAcr acrBase = new DeviceCodeAcr(deviceCode, userCode);
+        acrBase.setExpiresAt(Instant.now().plusSeconds(1800).toEpochMilli());
+        deviceCodeAcrRepository.store(acrBase);
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/AupController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/AupController.java
new file mode 100644
index 000000000..ac0ccb5b2
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/AupController.java
@@ -0,0 +1,162 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import cz.muni.ics.oidc.models.Aup;
+import cz.muni.ics.oidc.models.PerunAttribute;
+import cz.muni.ics.oidc.models.mappers.RpcMapper;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.SessionAttribute;
+
+/**
+ * Controller of the AUP page
+ *
+ * @author Dominik Baranek <baranek@ics.muni.cz>
+ */
+@Controller
+public class AupController {
+
+    public static final String URL = "aup";
+    public static final String NEW_AUPS = "newAups";
+    public static final String APPROVED = "approved";
+    public static final String RETURN_URL = "returnUrl";
+    public static final String USER_ATTR = "userAttr";
+
+    private static final SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd");
+    private static final Logger log = LoggerFactory.getLogger(AupController.class);
+
+    private final JsonNodeFactory jsonNodeFactory = JsonNodeFactory.instance;
+    private final ObjectMapper mapper = new ObjectMapper();
+
+    @Autowired
+    private PerunAdapter perunAdapter;
+
+    @Autowired
+    private PerunOidcConfig perunOidcConfig;
+
+    @Autowired
+    private Localization localization;
+
+    @Autowired
+    private WebHtmlClasses htmlClasses;
+
+    @GetMapping(value = "/" + URL)
+    public String showAup(HttpServletRequest request, Map<String, Object> model,
+                          @SessionAttribute(name = NEW_AUPS) String newAupsString) throws IOException
+    {
+        JsonNode newAupsJson = mapper.readTree(newAupsString);
+        Map<String, Aup> newAups = new LinkedHashMap<>();
+
+        Iterator<Map.Entry<String, JsonNode>> iterator = newAupsJson.fields();
+        while (iterator.hasNext()) {
+            Map.Entry<String, JsonNode> keyAupPair = iterator.next();
+            newAups.put(keyAupPair.getKey(), RpcMapper.mapAup(keyAupPair.getValue()));
+        }
+
+        model.put(NEW_AUPS, newAups);
+        ControllerUtils.setPageOptions(model, request, localization, htmlClasses, perunOidcConfig);
+
+        return "aup";
+    }
+
+    @PostMapping(value = "/" + URL, consumes = "application/x-www-form-urlencoded")
+    public String storeAup(HttpServletRequest request,
+                           @SessionAttribute String returnUrl,
+                           @SessionAttribute(name = NEW_AUPS) String newAupsString,
+                           @SessionAttribute(name = USER_ATTR) String userAupsAttrName) throws IOException
+    {
+        JsonNode aupsToApproveJson = mapper.readTree(newAupsString);
+        ObjectNode aupsToApproveJsonObject = new ObjectNode(jsonNodeFactory);
+
+        Iterator<Map.Entry<String, JsonNode>> iterator = aupsToApproveJson.fields();
+        while (iterator.hasNext()) {
+            Map.Entry<String, JsonNode> keyAupPair = iterator.next();
+            ObjectNode aup = (ObjectNode) keyAupPair.getValue();
+
+            Date date = new Date(System.currentTimeMillis());
+            aup.put(Aup.SIGNED_ON, formatter.format(date));
+
+            if (aupsToApproveJsonObject.has(keyAupPair.getKey())) {
+                aupsToApproveJsonObject.replace(keyAupPair.getKey(), aup);
+            } else {
+                aupsToApproveJsonObject.set(keyAupPair.getKey(), aup);
+            }
+        }
+
+        Long userId = Long.parseLong(request.getUserPrincipal().getName());
+
+        try {
+            PerunAttribute userAupsAttr = perunAdapter.getAdapterRpc().getUserAttribute(userId, userAupsAttrName);
+            if (userAupsAttr != null) {
+                Map<String, String> userAupsAttrValue = new LinkedHashMap<>();
+                if (userAupsAttr.valueAsMap() != null) {
+                    userAupsAttrValue = userAupsAttr.valueAsMap();
+                }
+
+                ObjectNode userAupsAttrAsObjNode = mapper.convertValue(userAupsAttrValue, ObjectNode.class);
+
+                ObjectNode userAttrValueUpdated = updateUserAupsAttrValue(userAupsAttrAsObjNode, aupsToApproveJsonObject);
+                userAupsAttr.setValue(userAupsAttr.getType(), userAttrValueUpdated);
+                perunAdapter.getAdapterRpc().setUserAttribute(userId, userAupsAttr);
+            }
+        } catch (Exception e) {
+            log.warn("Exception when storing aup, probably RPC not reachable", e);
+        }
+
+        request.getSession().removeAttribute(NEW_AUPS);
+        request.getSession().removeAttribute(RETURN_URL);
+        request.getSession().removeAttribute(USER_ATTR);
+        request.getSession().setAttribute(APPROVED, true);
+
+        return "redirect:" + returnUrl;
+    }
+
+    private ObjectNode updateUserAupsAttrValue(ObjectNode userAups, ObjectNode newAups) throws IOException {
+        if (userAups == null) {
+            userAups = new ObjectNode(jsonNodeFactory);
+        }
+
+        Iterator<Map.Entry<String, JsonNode>> newAupsFields = newAups.fields();
+
+        while (newAupsFields.hasNext()) {
+            Map.Entry<String, JsonNode> voNameToListOfAups = newAupsFields.next();
+            String aupKey = voNameToListOfAups.getKey();
+            ObjectNode newApprovedAup = (ObjectNode) voNameToListOfAups.getValue();
+
+            ArrayNode oldAupsArray = null;
+
+            if (userAups.get(voNameToListOfAups.getKey()) != null) {
+                String oldAupsAsString = userAups.get(aupKey).asText();
+                oldAupsArray = (ArrayNode) mapper.readTree(oldAupsAsString);
+            }
+
+            if (oldAupsArray  == null) {
+                oldAupsArray = new ArrayNode(jsonNodeFactory);
+            }
+
+            oldAupsArray.add(newApprovedAup);
+            userAups.put(aupKey, oldAupsArray.toString());
+        }
+
+        return userAups;
+    }
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ControllerUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ControllerUtils.java
new file mode 100644
index 000000000..861c6f0d3
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/ControllerUtils.java
@@ -0,0 +1,304 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import cz.muni.ics.oidc.server.PerunScopeClaimTranslationService;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URIBuilder;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility class with common methods used for Controllers.
+ *
+ * @author Dominik Frantisek Bucik (bucik@ics.muni.cz)
+ */
+public class ControllerUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(ControllerUtils.class);
+
+    private static final String LANG_KEY = "lang";
+    private static final String REQ_URL_KEY = "reqURL";
+    private static final String LANGS_MAP_KEY = "langsMap";
+    public static final String LANG_PROPS_KEY = "langProps";
+
+    /**
+     * Set language properties for page.
+
+     * @param model model object
+     * @param req request object
+     * @param localization localization with texts
+     */
+    public static void setLanguageForPage(Map<String, Object> model, HttpServletRequest req,
+                                          Localization localization) {
+        String langFromParam = req.getParameter(LANG_KEY);
+        String browserLang = req.getLocale().getLanguage();
+
+        List<String> enabledLangs = localization.getEnabledLanguages();
+        String langKey = "en";
+
+        if (langFromParam != null
+            && enabledLangs.stream().anyMatch(x -> x.equalsIgnoreCase(langFromParam))) {
+            langKey = langFromParam;
+        } else if (enabledLangs.stream().anyMatch(x -> x.equalsIgnoreCase(browserLang))) {
+            langKey = browserLang;
+        }
+
+        String reqUrl = req.getRequestURL().toString();
+
+        if (!Strings.isNullOrEmpty(req.getQueryString())) {
+            reqUrl += ('?' + req.getQueryString());
+        }
+
+        try {
+            reqUrl = removeQueryParameter(reqUrl, LANG_KEY);
+        } catch (URISyntaxException e) {
+            log.warn("Could not remove lang param");
+        }
+
+        Properties langProperties = localization.getLocalizationFiles().get(langKey);
+
+        model.put(LANG_KEY, langKey);
+        model.put(REQ_URL_KEY, reqUrl);
+        model.put(LANGS_MAP_KEY, localization.getEntriesAvailable());
+        model.put(LANG_PROPS_KEY, langProperties);
+    }
+
+    /**
+     * Create redirect URL.
+     *
+     * @param request     Request object
+     * @param removedPart Part of URL to be removed
+     * @param pathPart    What to include as Path
+     * @param params      Map object of parameters
+     * @return Modified redirect URL
+     */
+    public static String createRedirectUrl(HttpServletRequest request, String removedPart,
+                                           String pathPart, Map<String, String> params) {
+        String baseUrl = request.getRequestURL().toString();
+        int endIndex = baseUrl.indexOf(removedPart);
+        if (endIndex > 1) {
+            baseUrl = baseUrl.substring(0, endIndex);
+        }
+
+        StringBuilder builder = new StringBuilder();
+        builder.append(baseUrl);
+        builder.append(pathPart);
+        if (!params.isEmpty()) {
+            builder.append('?');
+            for (Map.Entry<String, String> entry : params.entrySet()) {
+                try {
+                    String encodedParamVal =
+                        URLEncoder.encode(entry.getValue(), String.valueOf(StandardCharsets.UTF_8));
+                    builder.append(entry.getKey());
+                    builder.append('=');
+                    builder.append(encodedParamVal);
+                    builder.append('&');
+                } catch (UnsupportedEncodingException e) {
+                    log.warn("Failed to encode param: {}, {}", entry.getKey(), entry.getValue());
+                }
+            }
+            builder.deleteCharAt(builder.length() - 1);
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * Set all options for page.
+
+     * @param model model object
+     * @param req request object
+     * @param localization localization with texts
+     * @param classes additional html classes
+     * @param perunOidcConfig oidc config class
+     */
+    public static void setPageOptions(Map<String, Object> model, HttpServletRequest req,
+                                      Localization localization,
+                                      WebHtmlClasses classes, PerunOidcConfig perunOidcConfig) {
+        setLanguageForPage(model, req, localization);
+        model.put("classes", classes.getWebHtmlClassesProperties());
+        model.put("theme", perunOidcConfig.getTheme().toLowerCase());
+        model.put("baseURL", perunOidcConfig.getBaseURL());
+        model.put("samlResourcesURL", perunOidcConfig.getSamlResourcesURL());
+        model.put("contactMail", perunOidcConfig.getEmailContact());
+    }
+
+    /**
+     * Set scopes and claims for consent page.
+
+     * @param scopeService service for working with scopes
+     * @param translationService scope to claim translation service
+     * @param model model object
+     * @param scope set of scopes
+     * @param user userInfo object
+     */
+    public static void setScopesAndClaims(SystemScopeService scopeService,
+                                          PerunScopeClaimTranslationService translationService,
+                                          Map<String, Object> model,
+                                          Set<String> scope,
+                                          UserInfo user) {
+        Set<SystemScope> scopes = scopeService.fromStrings(scope);
+        Set<SystemScope> sortedScopes = new LinkedHashSet<>(scopes.size());
+        Set<SystemScope> systemScopes = scopeService.getAll();
+
+        // sort scopes for display based on the inherent order of system scopes
+        for (SystemScope s : systemScopes) {
+            if (scopes.contains(s)) {
+                sortedScopes.add(s);
+            }
+        }
+
+        // add in any scopes that aren't system scopes to the end of the list
+        sortedScopes.addAll(Sets.difference(scopes, systemScopes));
+
+        Map<String, Map<String, Object>> claimsForScopes = new LinkedHashMap<>();
+        if (user != null) {
+            JsonObject userJson = user.toJson();
+            for (SystemScope systemScope : sortedScopes) {
+                Map<String, Object> claimValues = new LinkedHashMap<>();
+                Set<String> claims =
+                    translationService.getClaimsForScope(systemScope.getValue());
+                for (String claim : claims) {
+                    if (userJson.has(claim)) {
+                        JsonElement claimJson = userJson.get(claim);
+                        if (claimJson == null || claimJson.isJsonNull()) {
+                            continue;
+                        }
+                        if (claimJson.isJsonPrimitive()) {
+                            claimValues.put(claim, claimJson.getAsString());
+                        } else if (claimJson.isJsonArray()) {
+                            JsonArray arr = userJson.getAsJsonArray(claim);
+                            List<String> values = new ArrayList<>();
+                            for (int i = 0; i < arr.size(); i++) {
+                                values.add(arr.get(i).getAsString());
+                            }
+                            claimValues.put(claim, values);
+                        }
+                    }
+                }
+                claimsForScopes.put(systemScope.getValue(), claimValues);
+            }
+        }
+
+        sortedScopes = sortedScopes.stream()
+            .filter(systemScope -> {
+                if ("offline_access".equalsIgnoreCase(systemScope.getValue())) {
+                    claimsForScopes.put("offline_access",
+                        Collections.singletonMap("offline_access", true));
+                    return true;
+                }
+                return claimsForScopes.containsKey(systemScope.getValue());
+            })
+            .sorted((o1, o2) -> compareByClaimsAmount(o1, o2, claimsForScopes))
+            .collect(Collectors.toCollection(LinkedHashSet::new));
+        model.put("claims", claimsForScopes);
+        model.put("scopes", sortedScopes);
+    }
+
+    /**
+     * Create URL form base and parameters.
+
+     * @param base String with base of the URL
+     * @param params Map of parameters to be added
+     * @return constructed URL
+     */
+    public static String createUrl(String base, Map<String, String> params) {
+        String url = base;
+        if (!params.isEmpty()) {
+            url += '?';
+            StringBuilder sb = new StringBuilder(url);
+            Iterator<Map.Entry<String, String>> it = params.entrySet().iterator();
+            if (it.hasNext()) {
+                while (it.hasNext()) {
+                    Map.Entry<String, String> param = it.next();
+                    try {
+                        if (param.getKey() != null && param.getValue() != null) {
+                            String encodedValue = URLEncoder.encode(param.getValue(),
+                                StandardCharsets.UTF_8.toString());
+                            sb.append(param.getKey()).append('=').append(encodedValue);
+                        }
+                    } catch (UnsupportedEncodingException e) {
+                        //TODO: handle
+                    }
+                    if (it.hasNext()) {
+                        sb.append('&');
+                    }
+                }
+                url = sb.toString();
+            }
+        }
+        return url;
+    }
+
+    /**
+     * Reconstruct request URL.
+
+     * @param oidcConfig oidc config object
+     * @param newPath new path to be appended
+     * @return request URL
+     */
+    public static String constructRequestUrl(PerunOidcConfig oidcConfig, String newPath) {
+        String url = oidcConfig.getConfigBean().getIssuer();
+        newPath = (url.endsWith("/") ? newPath.replaceFirst("/", "") : newPath);
+        return url + newPath;
+    }
+
+    private static String removeQueryParameter(String url, String parameterName)
+        throws URISyntaxException {
+        URIBuilder uriBuilder = new URIBuilder(url);
+        List<NameValuePair> queryParameters = uriBuilder.getQueryParams()
+            .stream()
+            .filter(p -> !p.getName().equals(parameterName))
+            .collect(Collectors.toList());
+        if (queryParameters.isEmpty()) {
+            uriBuilder.removeQuery();
+        } else {
+            uriBuilder.setParameters(queryParameters);
+        }
+        return uriBuilder.build().toString();
+    }
+
+    private static int compareByClaimsAmount(SystemScope o1, SystemScope o2,
+                                             Map<String, Map<String, Object>> claimsForScopes) {
+        int o1ClaimsSize = claimsForScopes.getOrDefault(o1.getValue(),
+            new LinkedHashMap<>()).size();
+        int o2ClaimsSize = claimsForScopes.getOrDefault(o2.getValue(),
+            new LinkedHashMap<>()).size();
+        int compare = Integer.compare(o1ClaimsSize, o2ClaimsSize);
+        if (o1ClaimsSize == 0 && compare == 0) {
+            return 0;
+        } else if (o1ClaimsSize == 0) {
+            return 1;
+        } else if (o2ClaimsSize == 0) {
+            return -1;
+        } else {
+            return compare;
+        }
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/IsTestSpController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/IsTestSpController.java
new file mode 100644
index 000000000..09b0508a0
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/IsTestSpController.java
@@ -0,0 +1,68 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_ACCEPTED;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_TARGET;
+
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ *  Controller for IS TEST SP pages.
+ *
+ * @author Pavol Pluta <pavol.pluta1@gmail.com>
+ */
+@Controller
+public class IsTestSpController {
+    private static final Logger log = LoggerFactory.getLogger(IsTestSpController.class);
+
+    public static final String MAPPING = "/testRpWarning";
+    public static final String IS_TEST_SP_APPROVED_SESS = "isTestSpApprovedSession";
+    private static final String TARGET = "target";
+    private static final String ACTION = "action";
+
+    private final Localization localization;
+    private final WebHtmlClasses htmlClasses;
+    private final PerunOidcConfig perunOidcConfig;
+
+    @Autowired
+    public IsTestSpController(Localization localization, WebHtmlClasses htmlClasses, PerunOidcConfig perunOidcConfig) {
+        this.localization = localization;
+        this.htmlClasses = htmlClasses;
+        this.perunOidcConfig = perunOidcConfig;
+    }
+
+    @GetMapping(value = MAPPING, params = PARAM_TARGET)
+    public String isTestSpWarning(HttpServletRequest req,
+                                  Map<String, Object> model,
+                                  @RequestParam(PARAM_TARGET) String returnUrl)
+    {
+        log.debug("Display warning page for isTestSp");
+        model.put(TARGET, returnUrl);
+        model.put(ACTION, req.getRequestURL().toString());
+        ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+        return "isTestSpWarning";
+    }
+
+    @GetMapping(value = MAPPING, params = {PARAM_TARGET, PARAM_ACCEPTED})
+    public String warningApproved(HttpServletRequest request,
+                                  @RequestParam(PARAM_TARGET) String target)
+    {
+        log.debug("Warning approved, set session attribute and redirect to {}", target);
+        HttpSession sess = request.getSession();
+        if (sess != null) {
+            sess.setAttribute(IS_TEST_SP_APPROVED_SESS, true);
+        }
+        return "redirect:" + target;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/LoginController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/LoginController.java
new file mode 100644
index 000000000..af8c10a5b
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/LoginController.java
@@ -0,0 +1,45 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class LoginController {
+
+	private static final Logger log = LoggerFactory.getLogger(LoginController.class);
+
+	public static final String MAPPING_SUCCESS = "/login_success";
+	public static final String MAPPING_FAILURE = "/login_failure";
+
+	private final Localization localization;
+	private final WebHtmlClasses htmlClasses;
+	private final PerunOidcConfig perunOidcConfig;
+
+	@Autowired
+	public LoginController(PerunOidcConfig perunOidcConfig, Localization localization, WebHtmlClasses htmlClasses) {
+		this.perunOidcConfig = perunOidcConfig;
+		this.localization = localization;
+		this.htmlClasses = htmlClasses;
+	}
+
+	@RequestMapping(value = MAPPING_SUCCESS)
+	public String loginSuccess(HttpServletRequest req, Map<String, Object> model) {
+		ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+		return "login_success";
+	}
+
+	@RequestMapping(value = MAPPING_FAILURE)
+	public String loginFailure(HttpServletRequest req, Map<String, Object> model) {
+		ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+		return "login_failure";
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/LogoutController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/LogoutController.java
new file mode 100644
index 000000000..07b98fd15
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/LogoutController.java
@@ -0,0 +1,38 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+public class LogoutController {
+
+	private static final Logger log = LoggerFactory.getLogger(LogoutController.class);
+
+	public static final String MAPPING_SUCCESS = "/logout_success";
+
+	private final Localization localization;
+	private final WebHtmlClasses htmlClasses;
+	private final PerunOidcConfig perunOidcConfig;
+
+	@Autowired
+	public LogoutController(PerunOidcConfig perunOidcConfig, Localization localization, WebHtmlClasses htmlClasses) {
+		this.perunOidcConfig = perunOidcConfig;
+		this.localization = localization;
+		this.htmlClasses = htmlClasses;
+	}
+
+	@RequestMapping(value = MAPPING_SUCCESS)
+	public String logoutSuccess(HttpServletRequest req, Map<String, Object> model) {
+		ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+		return "logout_success";
+	}
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunOAuthConfirmationController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunOAuthConfirmationController.java
new file mode 100644
index 000000000..64189e0e5
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunOAuthConfirmationController.java
@@ -0,0 +1,83 @@
+package cz.muni.ics.oidc.web.controllers;
+
+
+import cz.muni.ics.oidc.server.PerunScopeClaimTranslationService;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.server.userInfo.PerunUserInfo;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.security.Principal;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.oauth2.web.OAuthConfirmationController;
+import cz.muni.ics.openid.connect.service.UserInfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.oauth2.provider.AuthorizationRequest;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.SessionAttributes;
+
+/**
+ * Controller of the pages where user accepts that information
+ * about him will be sent to the client.
+ *
+ * @author Dominik František Bučík <bucik@ics.muni.cz>
+ * @author Peter Jancus <jancus@ics.muni.cz>
+ */
+@Controller
+@SessionAttributes("authorizationRequest")
+public class PerunOAuthConfirmationController{
+
+    public static final String APPROVE = "approve";
+
+    private final OAuthConfirmationController oAuthConfirmationController;
+    private final UserInfoService userInfoService;
+    private final PerunOidcConfig perunOidcConfig;
+    private final SystemScopeService scopeService;
+    private final PerunScopeClaimTranslationService scopeClaimTranslationService;
+    private final Localization localization;
+    private final WebHtmlClasses htmlClasses;
+
+    @Autowired
+    public PerunOAuthConfirmationController(OAuthConfirmationController oAuthConfirmationController,
+                                            UserInfoService userInfoService,
+                                            PerunOidcConfig perunOidcConfig,
+                                            SystemScopeService scopeService,
+                                            PerunScopeClaimTranslationService scopeClaimTranslationService,
+                                            Localization localization,
+                                            WebHtmlClasses htmlClasses)
+    {
+        this.oAuthConfirmationController = oAuthConfirmationController;
+        this.userInfoService = userInfoService;
+        this.perunOidcConfig = perunOidcConfig;
+        this.scopeService = scopeService;
+        this.scopeClaimTranslationService = scopeClaimTranslationService;
+        this.localization = localization;
+        this.htmlClasses = htmlClasses;
+    }
+
+    @RequestMapping(value = "/oauth/confirm_access", params = { "client_id" })
+    public String confirmAccess(Map<String, Object> model, HttpServletRequest req, Principal p)
+    {
+        AuthorizationRequest authRequest = (AuthorizationRequest)model.get("authorizationRequest");
+        String result = oAuthConfirmationController.confirmAccess(model, p);
+        if (result.equals(APPROVE) && !perunOidcConfig.getTheme().equalsIgnoreCase("default")) {
+            model.remove("scopes");
+            model.remove("claims");
+            ClientDetailsEntity client = (ClientDetailsEntity) model.get("client");
+            PerunUserInfo user = (PerunUserInfo) userInfoService.getByUsernameAndClientId(
+                    p.getName(), client.getClientId());
+            ControllerUtils.setScopesAndClaims(scopeService, scopeClaimTranslationService, model, authRequest.getScope(),
+                    user);
+            ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+
+            model.put("page", "consent");
+            return "themedApprove";
+        }
+
+        return result;
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedController.java
new file mode 100644
index 000000000..bf8fc06ca
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedController.java
@@ -0,0 +1,287 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_CLIENT_ID;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_HEADER;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_MESSAGE;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_REASON;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_TARGET;
+import static cz.muni.ics.oidc.web.controllers.ControllerUtils.LANG_PROPS_KEY;
+
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.util.Map;
+import java.util.Properties;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * Ctonroller for the unapproved page.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Controller
+public class PerunUnapprovedController {
+
+    private final static Logger log = LoggerFactory.getLogger(PerunUnapprovedController.class);
+
+    public static final String UNAPPROVED_MAPPING = "/unapproved";
+    public static final String UNAPPROVED_SPECIFIC_MAPPING = "/unapproved_spec";
+    public static final String UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING = "/unapprovedIce";
+    public static final String UNAPPROVED_ENSURE_VO_MAPPING = "/unapprovedEnsureVo";
+    public static final String UNAPPROVED_AUTHORIZATION = "/unapprovedAuthorization";
+    public static final String UNAPPROVED_NOT_IN_TEST_VOS_GROUPS = "/unapprovedNotInTestVosGroups";
+    public static final String UNAPPROVED_NOT_IN_PROD_VOS_GROUPS = "/unapprovedNotInProdVosGroups";
+    public static final String UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS = "/unapprovedNotInMandatoryVosGroups";
+    public static final String UNAPPROVED_NOT_LOGGED_IN = "/unapprovedNotLoggedIn";
+
+    public static final String REASON_NOT_SET = "notSet";
+    public static final String REASON_EXPIRED = "expired";
+
+    private static final String OUT_HEADER = "outHeader";
+    private static final String OUT_MESSAGE = "outMessage";
+    private static final String OUT_CONTACT_P = "outContactP";
+
+    private static final String ENSURE_VO_HDR = "403_ensure_vo_hdr";
+    private static final String ENSURE_VO_MSG = "403_ensure_vo_msg";
+
+    private static final String AUTHORIZATION_HDR = "403_authorization_hdr";
+    private static final String AUTHORIZATION_MSG = "403_authorization_msg";
+
+    private static final String ICE_NOT_SET_HDR = "403_isCesnetEligible_notSet_hdr";
+    private static final String ICE_NOT_SET_MSG = "403_isCesnetEligible_notSet_msg";
+    private static final String ICE_EXPIRED_HDR = "403_isCesnetEligible_expired_hdr";
+    private static final String ICE_EXPIRED_MSG = "403_isCesnetEligible_expired_msg";
+
+    private static final String NOT_IN_TEST_VOS_GROUPS_HDR = "403_not_in_test_vos_groups_hdr";
+    private static final String NOT_IN_TEST_VOS_GROUPS_MSG = "403_not_in_test_vos_groups_msg";
+
+    private static final String NOT_IN_PROD_VOS_GROUPS_HDR = "403_not_in_prod_vos_groups_hdr";
+    private static final String NOT_IN_PROD_VOS_GROUPS_MSG = "403_not_in_prod_vos_groups_msg";
+
+    private static final String NOT_IN_MANDATORY_VOS_GROUPS_HDR = "403_not_in_mandatory_vos_groups_hdr";
+    private static final String NOT_IN_MANDATORY_VOS_GROUPS_MSG = "403_not_in_mandatory_vos_groups_msg";
+
+    private static final String NOT_LOGGED_IN_HDR = "403_not_logged_in_hdr";
+    private static final String NOT_LOGGED_IN_MSG = "403_not_logged_in_msg";
+
+    private static final String CONTACT_PLACEHOLDER = "%%CONTACT_EMAIL%%";
+    private static final String TARGET_URL_PLACEHOLDER = "%%TARGET%%";
+    private static final String CONTACT_LANG_PROP_KEY = "contact_p";
+    private static final String CONTACT_MAIL = "contactMail";
+
+    @Autowired
+    private ClientDetailsEntityService clientService;
+
+    @Autowired
+    private PerunOidcConfig perunOidcConfig;
+
+    @Autowired
+    private Localization localization;
+
+    @Autowired
+    private WebHtmlClasses htmlClasses;
+
+    @GetMapping(value = UNAPPROVED_MAPPING)
+    public String showUnapproved(ServletRequest req, Map<String, Object> model,
+                                 @RequestParam(PARAM_CLIENT_ID) String clientId) {
+        HttpServletRequest request = (HttpServletRequest) req;
+        ClientDetailsEntity client;
+
+        try {
+            client = clientService.loadClientByClientId(clientId);
+        } catch (OAuth2Exception e) {
+            log.error("showUnapproved: OAuth2Exception was thrown when attempting to load client", e);
+            model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
+            return HttpCodeView.VIEWNAME;
+        } catch (IllegalArgumentException e) {
+            log.error("showUnapproved: IllegalArgumentException was thrown when attempting to load client", e);
+            model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
+            return HttpCodeView.VIEWNAME;
+        }
+
+        if (client == null) {
+            log.error("showUnapproved: could not find client " + clientId);
+            model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
+            return HttpCodeView.VIEWNAME;
+        }
+
+        ControllerUtils.setPageOptions(model, request, localization, htmlClasses, perunOidcConfig);
+        model.put("client", client);
+
+        return "unapproved";
+    }
+
+    @GetMapping(value = UNAPPROVED_SPECIFIC_MAPPING)
+    public String showUnapprovedSpec(ServletRequest req, Map<String, Object> model,
+                                     @RequestParam(value = PARAM_HEADER, required = false) String header,
+                                     @RequestParam(value = PARAM_MESSAGE, required = false) String message) {
+
+        ControllerUtils.setPageOptions(model, (HttpServletRequest) req, localization, htmlClasses, perunOidcConfig);
+
+        String headerText = getText(model, header);
+        String messageText = getText(model, message);
+        String contactPText = getText(model, CONTACT_LANG_PROP_KEY);
+
+        model.put(OUT_HEADER, headerText);
+        model.put(OUT_MESSAGE, messageText);
+        model.put(OUT_CONTACT_P, contactPText);
+        model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+
+        return "unapproved_spec";
+    }
+
+    @GetMapping(value = UNAPPROVED_IS_CESNET_ELIGIBLE_MAPPING)
+    public String showUnapprovedIsCesnetEligible(ServletRequest req, Map<String, Object> model,
+                                                 @RequestParam(value = PARAM_TARGET) String target,
+                                                 @RequestParam(value = PARAM_REASON) String reason) {
+
+        ControllerUtils.setPageOptions(model, (HttpServletRequest) req, localization, htmlClasses, perunOidcConfig);
+
+        String header;
+        String message;
+        String contactPText = getText(model, CONTACT_LANG_PROP_KEY);
+
+        if (REASON_EXPIRED.equals(reason)) {
+            header = getText(model, ICE_EXPIRED_HDR);
+            message = getText(model, ICE_EXPIRED_MSG);
+        } else if (REASON_NOT_SET.equals(reason)){
+            header = getText(model, ICE_NOT_SET_HDR);
+            message = getText(model, ICE_NOT_SET_MSG);
+        } else {
+            model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
+            return HttpCodeView.VIEWNAME;
+        }
+
+        header = replace(header, TARGET_URL_PLACEHOLDER, target);
+        message = replace(message, TARGET_URL_PLACEHOLDER, target);
+
+        model.put(OUT_HEADER, header);
+        model.put(OUT_MESSAGE, message);
+        model.put(OUT_CONTACT_P, contactPText);
+        model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+
+        return "unapproved_spec";
+    }
+
+    @GetMapping(value = UNAPPROVED_ENSURE_VO_MAPPING)
+    public String showUnapprovedEnsureVo(ServletRequest req, Map<String, Object> model) {
+        ControllerUtils.setPageOptions(model, (HttpServletRequest) req, localization, htmlClasses, perunOidcConfig);
+
+        String header = getText(model, ENSURE_VO_HDR);
+        String message = getText(model, ENSURE_VO_MSG);
+        String contactPText = getText(model, CONTACT_LANG_PROP_KEY);
+
+        model.put(OUT_HEADER, header);
+        model.put(OUT_MESSAGE, message);
+        model.put(OUT_CONTACT_P, contactPText);
+        model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+
+        return "unapproved_spec";
+    }
+
+    @GetMapping(value = UNAPPROVED_AUTHORIZATION)
+    public String showUnapprovedAuthorization(ServletRequest req, Map<String, Object> model) {
+        ControllerUtils.setPageOptions(model, (HttpServletRequest) req, localization, htmlClasses, perunOidcConfig);
+
+        String header = getText(model, AUTHORIZATION_HDR);
+        String message = getText(model, AUTHORIZATION_MSG);
+        String contactPText = getText(model, CONTACT_LANG_PROP_KEY);
+
+        model.put(OUT_HEADER, header);
+        model.put(OUT_MESSAGE, message);
+        model.put(OUT_CONTACT_P, contactPText);
+        model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+
+        return "unapproved_spec";
+    }
+
+    @GetMapping(value = UNAPPROVED_NOT_IN_TEST_VOS_GROUPS)
+    public String showUnapprovedNotInTestVosGroups(ServletRequest req, Map<String, Object> model) {
+        ControllerUtils.setPageOptions(model, (HttpServletRequest) req, localization, htmlClasses, perunOidcConfig);
+
+        String header = getText(model, NOT_IN_TEST_VOS_GROUPS_HDR);
+        String message = getText(model, NOT_IN_TEST_VOS_GROUPS_MSG);
+        String contactPText = getText(model, CONTACT_LANG_PROP_KEY);
+
+        model.put(OUT_HEADER, header);
+        model.put(OUT_MESSAGE, message);
+        model.put(OUT_CONTACT_P, contactPText);
+        model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+
+        return "unapproved_spec";
+    }
+
+    @GetMapping(value = UNAPPROVED_NOT_IN_PROD_VOS_GROUPS)
+    public String showUnapprovedNotInProdVosGroups(ServletRequest req, Map<String, Object> model) {
+        ControllerUtils.setPageOptions(model, (HttpServletRequest) req, localization, htmlClasses, perunOidcConfig);
+
+        String header = getText(model, NOT_IN_PROD_VOS_GROUPS_HDR);
+        String message = getText(model, NOT_IN_PROD_VOS_GROUPS_MSG);
+        String contactPText = getText(model, CONTACT_LANG_PROP_KEY);
+
+        model.put(OUT_HEADER, header);
+        model.put(OUT_MESSAGE, message);
+        model.put(OUT_CONTACT_P, contactPText);
+        model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+
+        return "unapproved_spec";
+    }
+
+    @GetMapping(value = UNAPPROVED_NOT_IN_MANDATORY_VOS_GROUPS)
+    public String showUnapprovedNotInMandatoryVosGroups(ServletRequest req, Map<String, Object> model) {
+        ControllerUtils.setPageOptions(model, (HttpServletRequest) req, localization, htmlClasses, perunOidcConfig);
+
+        String header = getText(model, NOT_IN_MANDATORY_VOS_GROUPS_HDR);
+        String message = getText(model, NOT_IN_MANDATORY_VOS_GROUPS_MSG);
+        String contactPText = getText(model, CONTACT_LANG_PROP_KEY);
+
+        model.put(OUT_HEADER, header);
+        model.put(OUT_MESSAGE, message);
+        model.put(OUT_CONTACT_P, contactPText);
+        model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+
+        return "unapproved_spec";
+    }
+
+    @GetMapping(value = UNAPPROVED_NOT_LOGGED_IN)
+    public String showUnapprovedNotLoggedIn(ServletRequest req, Map<String, Object> model) {
+        ControllerUtils.setPageOptions(model, (HttpServletRequest) req, localization, htmlClasses, perunOidcConfig);
+
+        String header = getText(model, NOT_LOGGED_IN_HDR);
+        String message = getText(model, NOT_LOGGED_IN_MSG);
+        String contactPText = getText(model, CONTACT_LANG_PROP_KEY);
+
+        model.put(OUT_HEADER, header);
+        model.put(OUT_MESSAGE, message);
+        model.put(OUT_CONTACT_P, contactPText);
+        model.put(CONTACT_MAIL, perunOidcConfig.getEmailContact());
+
+        return "unapproved_spec";
+    }
+
+    private String getText(Map<String, Object> model, String key) {
+        Properties langProps = (Properties) model.get(LANG_PROPS_KEY);
+        return langProps.getProperty(key);
+    }
+
+    private String replace(String container, String key, String value) {
+        if (container.contains(key)) {
+            return container.replaceAll(key, value);
+        } else {
+            return container;
+        }
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedRegistrationController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedRegistrationController.java
new file mode 100644
index 000000000..0744659b5
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/PerunUnapprovedRegistrationController.java
@@ -0,0 +1,182 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import cz.muni.ics.oidc.models.Facility;
+import cz.muni.ics.oidc.models.Group;
+import cz.muni.ics.oidc.models.PerunAttributeValue;
+import cz.muni.ics.oidc.models.Vo;
+import cz.muni.ics.oidc.server.adapters.PerunAdapter;
+import cz.muni.ics.oidc.server.configurations.FacilityAttrsConfig;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * Controller for the unapproved page which offers registration.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Controller
+public class PerunUnapprovedRegistrationController {
+
+    private final static Logger log = LoggerFactory.getLogger(PerunUnapprovedRegistrationController.class);
+
+    public static final String REGISTRATION_FORM_MAPPING = "/regForm";
+    public static final String REGISTRATION_FORM_SUBMIT_MAPPING = "/regForm/submit";
+    public static final String REGISTRATION_CONTINUE_MAPPING = "/regForm/continue";
+
+    @Autowired
+    private ClientDetailsEntityService clientService;
+
+    @Autowired
+    private PerunAdapter perunAdapter;
+
+    @Autowired
+    private FacilityAttrsConfig facilityAttrsConfig;
+
+    @Autowired
+    private PerunOidcConfig perunOidcConfig;
+
+    @Autowired
+    private Localization localization;
+
+    @Autowired
+    private WebHtmlClasses htmlClasses;
+
+    @GetMapping(value = REGISTRATION_FORM_MAPPING)
+    public String showRegistrationForm(Map<String, Object> model, ServletRequest req, ServletResponse res,
+                                       @RequestParam("client_id") String clientId,
+                                       @RequestParam("facility_id") Long facilityId,
+                                       @RequestParam("user_id") Long userId) {
+        HttpServletRequest request = (HttpServletRequest) req;
+        HttpServletResponse response = (HttpServletResponse) res;
+        ClientDetailsEntity client;
+
+        try {
+            client = clientService.loadClientByClientId(clientId);
+        } catch (OAuth2Exception e) {
+            log.error("confirmAccess: OAuth2Exception was thrown when attempting to load client", e);
+            model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
+            return HttpCodeView.VIEWNAME;
+        } catch (IllegalArgumentException e) {
+            log.error("confirmAccess: IllegalArgumentException was thrown when attempting to load client", e);
+            model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST);
+            return HttpCodeView.VIEWNAME;
+        }
+
+        if (client == null) {
+            log.error("confirmAccess: could not find client {}", clientId);
+            model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND);
+            return HttpCodeView.VIEWNAME;
+        }
+
+        Facility facility = perunAdapter.getFacilityByClientId(clientId);
+        Map<String, PerunAttributeValue> facilityAttributes = perunAdapter.getFacilityAttributeValues(facility,
+                facilityAttrsConfig.getMembershipAttrNames());
+        List<String> voShortNames = facilityAttributes.get(facilityAttrsConfig.getVoShortNamesAttr()).valueAsList();
+        Map<Vo, List<Group>> groupsForRegistration = perunAdapter.getAdapterRpc()
+                .getGroupsForRegistration(facility, userId, voShortNames);
+        log.debug("groupsForReg: {}", groupsForRegistration);
+
+        if (groupsForRegistration.isEmpty()) {
+            String redirectUrl = ControllerUtils.createRedirectUrl(request, REGISTRATION_FORM_MAPPING,
+                    PerunUnapprovedController.UNAPPROVED_MAPPING, Collections.singletonMap("client_id", clientId));
+            response.reset();
+            response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+            response.setHeader("Location", redirectUrl);
+            return null;
+        } else if (groupsForRegistration.keySet().size() == 1) {
+            for (Map.Entry<Vo, List<Group>> entry: groupsForRegistration.entrySet()) {
+                // no other way how to extract the first item (as it is the only)
+                List<Group> groupList = groupsForRegistration.get(entry.getKey());
+                if (groupList.size() == 1) {
+                    Group group = groupList.get(0);
+                    String redirectUrl = createRegistrarUrl(entry.getKey(), group.getName());
+                    response.reset();
+                    response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+                    response.setHeader("Location", redirectUrl);
+                    return null;
+                }
+            }
+        }
+
+        ControllerUtils.setPageOptions(model, request, localization, htmlClasses, perunOidcConfig);
+        model.put("client", client);
+        model.put("facilityId", facilityId);
+        model.put("action", buildActionUrl(request));
+        model.put("groupsForRegistration", groupsForRegistration);
+        model.put("page", "regForm");
+
+        return "registrationForm";
+    }
+
+    @GetMapping(value = REGISTRATION_FORM_SUBMIT_MAPPING)
+    public void processRegistrationForm(@RequestParam("selectedGroup") String groupName,
+                                        @RequestParam("selectedVo") String voName,
+                                        ServletResponse res) throws IOException {
+        HttpServletResponse request = (HttpServletResponse) res;
+
+        groupName = groupName.split(":", 2)[1];
+
+        String redirectUrl = createRegistrarUrl(voName, groupName);
+
+        request.sendRedirect(redirectUrl);
+    }
+
+    @GetMapping(value = REGISTRATION_CONTINUE_MAPPING)
+    public String showContinuePage(Map<String, Object> model, ServletRequest req,
+                                   @RequestParam("client_id") String clientId,
+                                   @RequestParam("facility_id") Long facilityId,
+                                   @RequestParam("user_id") Long userId) {
+        HttpServletRequest request = (HttpServletRequest) req;
+
+        model.put("page", "regContinue");
+        model.put("client_id", clientId);
+        model.put("facility_id", facilityId);
+        model.put("user_id", userId);
+        model.put("action", request.getRequestURL().toString()
+                .replace(REGISTRATION_CONTINUE_MAPPING, REGISTRATION_FORM_MAPPING));
+        ControllerUtils.setPageOptions(model, request, localization, htmlClasses, perunOidcConfig);
+
+        return "registrationFormContinue";
+    }
+
+    private String createRegistrarUrl(Vo vo, String groupName) {
+        return createRegistrarUrl(vo.getShortName(), groupName);
+    }
+
+    private String createRegistrarUrl(String vohortName, String groupName) {
+        String redirectUrl = perunOidcConfig.getRegistrarUrl().concat("?vo=").concat(vohortName);
+        if (groupName != null && !groupName.isEmpty() && !groupName.equalsIgnoreCase("members")) {
+            redirectUrl = redirectUrl.concat("&group=").concat(groupName);
+        }
+
+        return redirectUrl;
+    }
+
+    private String buildActionUrl(HttpServletRequest request) {
+        int startIndex = request.getRequestURL().lastIndexOf(REGISTRATION_FORM_MAPPING);
+        int length = request.getRequestURL().length();
+        return request.getRequestURL().delete(startIndex, length)
+                .append(REGISTRATION_FORM_SUBMIT_MAPPING).toString();
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/RegistrationController.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/RegistrationController.java
new file mode 100644
index 000000000..c75dbfb72
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/controllers/RegistrationController.java
@@ -0,0 +1,47 @@
+package cz.muni.ics.oidc.web.controllers;
+
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * Controller for the unapproved page which offers registration.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Controller
+public class RegistrationController {
+
+    private final static Logger log = LoggerFactory.getLogger(RegistrationController.class);
+
+    public static final String PARAM_TARGET = "target";
+
+    public static final String CONTINUE_DIRECT_MAPPING = "/continueDirect";
+
+    @Autowired
+    private PerunOidcConfig perunOidcConfig;
+
+    @Autowired
+    private Localization localization;
+
+    @Autowired
+    private WebHtmlClasses htmlClasses;
+
+    @GetMapping(value = CONTINUE_DIRECT_MAPPING, params = { PARAM_TARGET })
+    public String showRegistrationForm(Map<String, Object> model, HttpServletRequest req,
+                                       @RequestParam(PARAM_TARGET) String target)
+    {
+        model.put(PARAM_TARGET, target);
+        ControllerUtils.setPageOptions(model, req, localization, htmlClasses, perunOidcConfig);
+        return "continue_direct";
+    }
+
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/langs/Localization.java b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/langs/Localization.java
new file mode 100644
index 000000000..8af48345c
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/oidc/web/langs/Localization.java
@@ -0,0 +1,105 @@
+package cz.muni.ics.oidc.web.langs;
+
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Static utility class for Language Bar displayed on custom pages.
+ *
+ * It contains mapping with language keys to language displayed names.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+public class Localization {
+
+	private static final Logger log = LoggerFactory.getLogger(Localization.class);
+
+	private Map<String, String> localizationEntries;
+	private Map<String, Properties> localizationFiles;
+	private final String localizationFilesPath;
+	private final List<String> enabledLanguages;
+
+	public Localization(PerunOidcConfig perunOidcConfig) {
+		this.enabledLanguages = perunOidcConfig.getAvailableLangs();
+		this.localizationFilesPath = perunOidcConfig.getLocalizationFilesPath();
+		this.initEntriesAndFiles();
+	}
+
+	public Map<String, String> getLocalizationEntries() {
+		return localizationEntries;
+	}
+
+	public Map<String, Properties> getLocalizationFiles() {
+		return localizationFiles;
+	}
+
+	public List<String> getEnabledLanguages() {
+		return enabledLanguages;
+	}
+
+	/**
+	 * Get mapping for the languages available
+	 * @return Map with key = language code, value = language displayed text
+	 */
+	public Map<String, String> getEntriesAvailable() {
+		Map<String, String> result = new HashMap<>();
+
+		for (String key: enabledLanguages) {
+			String lower = key.toLowerCase();
+			if (localizationEntries.containsKey(lower)) {
+				result.put(lower, localizationEntries.get(lower));
+			}
+		}
+
+		return result;
+	}
+
+	private void initEntriesAndFiles() {
+		localizationEntries = new HashMap<>();
+		localizationEntries.put("en", "English");
+		localizationEntries.put("cs", "Čeština");
+		localizationEntries.put("sk", "Slovenčina");
+
+		localizationFiles = new HashMap<>();
+		for (String lang: enabledLanguages) {
+			lang = lang.toLowerCase();
+			if (! localizationEntries.containsKey(lang)) {
+				continue;
+			}
+
+			Properties langProps = new Properties();
+			String resourceFileName = "localization/" + lang + ".properties";
+			try (InputStreamReader isr = new InputStreamReader(Objects.requireNonNull(
+					getClass().getClassLoader().getResourceAsStream(resourceFileName)), StandardCharsets.UTF_8)) {
+				langProps.load(isr);
+				log.debug("Loaded localization file: {}", resourceFileName);
+				localizationFiles.put(lang, langProps);
+			} catch (IOException e) {
+				log.warn("Exception caught when reading {}", resourceFileName, e);
+			}
+
+			String customFileName = localizationFilesPath + '/' +lang + ".properties";
+			try (InputStreamReader isr = new InputStreamReader(
+					new FileInputStream(customFileName), StandardCharsets.UTF_8
+			)) {
+				langProps.load(isr);
+				log.debug("Loaded localization file: {}", customFileName);
+			} catch (FileNotFoundException e) {
+				log.warn("File: {} not found", customFileName, e);
+			} catch (IOException e) {
+				log.warn("Exception caught when reading {}", customFileName, e);
+			}
+		}
+	}
+}
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessor.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/ClientDetailsEntityJsonProcessor.java
similarity index 53%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessor.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/ClientDetailsEntityJsonProcessor.java
index 6cb1b712a..a5a91f465 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessor.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/ClientDetailsEntityJsonProcessor.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect;
+package cz.muni.ics.openid.connect;
 
 
 import com.google.common.base.Joiner;
@@ -31,65 +31,59 @@ import com.google.gson.JsonParser;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTParser;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
-import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
-import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
-import org.mitre.oauth2.model.RegisteredClient;
+import cz.muni.ics.util.JsonUtils;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AppType;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AuthMethod;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.SubjectType;
+import cz.muni.ics.oauth2.model.RegisteredClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.text.ParseException;
 
-import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.CODE_CHALLENGE_METHOD;
-import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS;
-import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
-import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
-import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.JWKS;
-import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
-import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
-import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
-import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE_SEPARATOR;
-import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_ID;
-import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
-import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_VERSION;
-import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
-import static org.mitre.util.JsonUtils.getAsArray;
-import static org.mitre.util.JsonUtils.getAsDate;
-import static org.mitre.util.JsonUtils.getAsJweAlgorithm;
-import static org.mitre.util.JsonUtils.getAsJweEncryptionMethod;
-import static org.mitre.util.JsonUtils.getAsJwsAlgorithm;
-import static org.mitre.util.JsonUtils.getAsPkceAlgorithm;
-import static org.mitre.util.JsonUtils.getAsString;
-import static org.mitre.util.JsonUtils.getAsStringSet;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_ID;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_NAME;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CODE_CHALLENGE_METHOD;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CONTACTS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.GRANT_TYPES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.JWKS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.JWKS_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.POLICY_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUEST_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SCOPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SCOPE_SEPARATOR;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SOFTWARE_ID;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SOFTWARE_VERSION;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOS_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
+import static cz.muni.ics.util.JsonUtils.getAsArray;
 
 /**
  * Utility class to handle the parsing and serialization of ClientDetails objects.
@@ -114,31 +108,31 @@ public class ClientDetailsEntityJsonProcessor {
 			ClientDetailsEntity c = new ClientDetailsEntity();
 
 			// these two fields should only be sent in the update request, and MUST match existing values
-			c.setClientId(getAsString(o, CLIENT_ID));
-			c.setClientSecret(getAsString(o, CLIENT_SECRET));
+			c.setClientId(JsonUtils.getAsString(o, CLIENT_ID));
+			c.setClientSecret(JsonUtils.getAsString(o, CLIENT_SECRET));
 
 			// OAuth DynReg
-			c.setRedirectUris(getAsStringSet(o, REDIRECT_URIS));
-			c.setClientName(getAsString(o, CLIENT_NAME));
-			c.setClientUri(getAsString(o, CLIENT_URI));
-			c.setContacts(getAsStringSet(o, CONTACTS));
-			c.setTosUri(getAsString(o, TOS_URI));
+			c.setRedirectUris(JsonUtils.getAsStringSet(o, REDIRECT_URIS));
+			c.setClientName(JsonUtils.getAsString(o, CLIENT_NAME));
+			c.setClientUri(JsonUtils.getAsString(o, CLIENT_URI));
+			c.setContacts(JsonUtils.getAsStringSet(o, CONTACTS));
+			c.setTosUri(JsonUtils.getAsString(o, TOS_URI));
 
-			String authMethod = getAsString(o, TOKEN_ENDPOINT_AUTH_METHOD);
+			String authMethod = JsonUtils.getAsString(o, TOKEN_ENDPOINT_AUTH_METHOD);
 			if (authMethod != null) {
 				c.setTokenEndpointAuthMethod(AuthMethod.getByValue(authMethod));
 			}
 
 			// scope is a space-separated string
-			String scope = getAsString(o, SCOPE);
+			String scope = JsonUtils.getAsString(o, SCOPE);
 			if (scope != null) {
 				c.setScope(Sets.newHashSet(Splitter.on(SCOPE_SEPARATOR).split(scope)));
 			}
 
-			c.setGrantTypes(getAsStringSet(o, GRANT_TYPES));
-			c.setResponseTypes(getAsStringSet(o, RESPONSE_TYPES));
-			c.setPolicyUri(getAsString(o, POLICY_URI));
-			c.setJwksUri(getAsString(o, JWKS_URI));
+			c.setGrantTypes(JsonUtils.getAsStringSet(o, GRANT_TYPES));
+			c.setResponseTypes(JsonUtils.getAsStringSet(o, RESPONSE_TYPES));
+			c.setPolicyUri(JsonUtils.getAsString(o, POLICY_URI));
+			c.setJwksUri(JsonUtils.getAsString(o, JWKS_URI));
 
 			JsonElement jwksEl = o.get(JWKS);
 			if (jwksEl != null && jwksEl.isJsonObject()) {
@@ -152,29 +146,29 @@ public class ClientDetailsEntityJsonProcessor {
 			}
 
 			// OIDC Additions
-			String appType = getAsString(o, APPLICATION_TYPE);
+			String appType = JsonUtils.getAsString(o, APPLICATION_TYPE);
 			if (appType != null) {
 				c.setApplicationType(AppType.getByValue(appType));
 			}
 
-			c.setSectorIdentifierUri(getAsString(o, SECTOR_IDENTIFIER_URI));
+			c.setSectorIdentifierUri(JsonUtils.getAsString(o, SECTOR_IDENTIFIER_URI));
 
-			String subjectType = getAsString(o, SUBJECT_TYPE);
+			String subjectType = JsonUtils.getAsString(o, SUBJECT_TYPE);
 			if (subjectType != null) {
 				c.setSubjectType(SubjectType.getByValue(subjectType));
 			}
 
-			c.setRequestObjectSigningAlg(getAsJwsAlgorithm(o, REQUEST_OBJECT_SIGNING_ALG));
+			c.setRequestObjectSigningAlg(JsonUtils.getAsJwsAlgorithm(o, REQUEST_OBJECT_SIGNING_ALG));
 
-			c.setUserInfoSignedResponseAlg(getAsJwsAlgorithm(o, USERINFO_SIGNED_RESPONSE_ALG));
-			c.setUserInfoEncryptedResponseAlg(getAsJweAlgorithm(o, USERINFO_ENCRYPTED_RESPONSE_ALG));
-			c.setUserInfoEncryptedResponseEnc(getAsJweEncryptionMethod(o, USERINFO_ENCRYPTED_RESPONSE_ENC));
+			c.setUserInfoSignedResponseAlg(JsonUtils.getAsJwsAlgorithm(o, USERINFO_SIGNED_RESPONSE_ALG));
+			c.setUserInfoEncryptedResponseAlg(JsonUtils.getAsJweAlgorithm(o, USERINFO_ENCRYPTED_RESPONSE_ALG));
+			c.setUserInfoEncryptedResponseEnc(JsonUtils.getAsJweEncryptionMethod(o, USERINFO_ENCRYPTED_RESPONSE_ENC));
 
-			c.setIdTokenSignedResponseAlg(getAsJwsAlgorithm(o, ID_TOKEN_SIGNED_RESPONSE_ALG));
-			c.setIdTokenEncryptedResponseAlg(getAsJweAlgorithm(o, ID_TOKEN_ENCRYPTED_RESPONSE_ALG));
-			c.setIdTokenEncryptedResponseEnc(getAsJweEncryptionMethod(o, ID_TOKEN_ENCRYPTED_RESPONSE_ENC));
+			c.setIdTokenSignedResponseAlg(JsonUtils.getAsJwsAlgorithm(o, ID_TOKEN_SIGNED_RESPONSE_ALG));
+			c.setIdTokenEncryptedResponseAlg(JsonUtils.getAsJweAlgorithm(o, ID_TOKEN_ENCRYPTED_RESPONSE_ALG));
+			c.setIdTokenEncryptedResponseEnc(JsonUtils.getAsJweEncryptionMethod(o, ID_TOKEN_ENCRYPTED_RESPONSE_ENC));
 
-			c.setTokenEndpointAuthSigningAlg(getAsJwsAlgorithm(o, TOKEN_ENDPOINT_AUTH_SIGNING_ALG));
+			c.setTokenEndpointAuthSigningAlg(JsonUtils.getAsJwsAlgorithm(o, TOKEN_ENDPOINT_AUTH_SIGNING_ALG));
 
 			if (o.has(DEFAULT_MAX_AGE)) {
 				if (o.get(DEFAULT_MAX_AGE).isJsonPrimitive()) {
@@ -188,20 +182,20 @@ public class ClientDetailsEntityJsonProcessor {
 				}
 			}
 
-			c.setDefaultACRvalues(getAsStringSet(o, DEFAULT_ACR_VALUES));
-			c.setInitiateLoginUri(getAsString(o, INITIATE_LOGIN_URI));
-			c.setPostLogoutRedirectUris(getAsStringSet(o, POST_LOGOUT_REDIRECT_URIS));
-			c.setRequestUris(getAsStringSet(o, REQUEST_URIS));
+			c.setDefaultACRvalues(JsonUtils.getAsStringSet(o, DEFAULT_ACR_VALUES));
+			c.setInitiateLoginUri(JsonUtils.getAsString(o, INITIATE_LOGIN_URI));
+			c.setPostLogoutRedirectUris(JsonUtils.getAsStringSet(o, POST_LOGOUT_REDIRECT_URIS));
+			c.setRequestUris(JsonUtils.getAsStringSet(o, REQUEST_URIS));
 
-			c.setClaimsRedirectUris(getAsStringSet(o, CLAIMS_REDIRECT_URIS));
+			c.setClaimsRedirectUris(JsonUtils.getAsStringSet(o, CLAIMS_REDIRECT_URIS));
 
-			c.setCodeChallengeMethod(getAsPkceAlgorithm(o, CODE_CHALLENGE_METHOD));
+			c.setCodeChallengeMethod(JsonUtils.getAsPkceAlgorithm(o, CODE_CHALLENGE_METHOD));
 
-			c.setSoftwareId(getAsString(o, SOFTWARE_ID));
-			c.setSoftwareVersion(getAsString(o, SOFTWARE_VERSION));
+			c.setSoftwareId(JsonUtils.getAsString(o, SOFTWARE_ID));
+			c.setSoftwareVersion(JsonUtils.getAsString(o, SOFTWARE_VERSION));
 
 			// note that this does not process or validate the software statement, that's handled in other components
-			String softwareStatement = getAsString(o,  SOFTWARE_STATEMENT);
+			String softwareStatement = JsonUtils.getAsString(o,  SOFTWARE_STATEMENT);
 			if (!Strings.isNullOrEmpty(softwareStatement)) {
 				try {
 					JWT softwareStatementJwt = JWTParser.parse(softwareStatement);
@@ -238,10 +232,10 @@ public class ClientDetailsEntityJsonProcessor {
 
 			RegisteredClient rc = new RegisteredClient(c);
 			// get any fields from the registration
-			rc.setRegistrationAccessToken(getAsString(o, REGISTRATION_ACCESS_TOKEN));
-			rc.setRegistrationClientUri(getAsString(o, REGISTRATION_CLIENT_URI));
-			rc.setClientIdIssuedAt(getAsDate(o, CLIENT_ID_ISSUED_AT));
-			rc.setClientSecretExpiresAt(getAsDate(o, CLIENT_SECRET_EXPIRES_AT));
+			rc.setRegistrationAccessToken(JsonUtils.getAsString(o, REGISTRATION_ACCESS_TOKEN));
+			rc.setRegistrationClientUri(JsonUtils.getAsString(o, REGISTRATION_CLIENT_URI));
+			rc.setClientIdIssuedAt(JsonUtils.getAsDate(o, CLIENT_ID_ISSUED_AT));
+			rc.setClientSecretExpiresAt(JsonUtils.getAsDate(o, CLIENT_SECRET_EXPIRES_AT));
 
 			rc.setSource(o);
 
@@ -294,15 +288,15 @@ public class ClientDetailsEntityJsonProcessor {
 			// add in all other client properties
 
 			// OAuth DynReg
-			o.add(REDIRECT_URIS, getAsArray(c.getRedirectUris()));
+			o.add(REDIRECT_URIS, JsonUtils.getAsArray(c.getRedirectUris()));
 			o.addProperty(CLIENT_NAME, c.getClientName());
 			o.addProperty(CLIENT_URI, c.getClientUri());
-			o.add(CONTACTS, getAsArray(c.getContacts()));
+			o.add(CONTACTS, JsonUtils.getAsArray(c.getContacts()));
 			o.addProperty(TOS_URI, c.getTosUri());
 			o.addProperty(TOKEN_ENDPOINT_AUTH_METHOD, c.getTokenEndpointAuthMethod() != null ? c.getTokenEndpointAuthMethod().getValue() : null);
 			o.addProperty(SCOPE, c.getScope() != null ? Joiner.on(SCOPE_SEPARATOR).join(c.getScope()) : null);
-			o.add(GRANT_TYPES, getAsArray(c.getGrantTypes()));
-			o.add(RESPONSE_TYPES, getAsArray(c.getResponseTypes()));
+			o.add(GRANT_TYPES, JsonUtils.getAsArray(c.getGrantTypes()));
+			o.add(RESPONSE_TYPES, JsonUtils.getAsArray(c.getResponseTypes()));
 			o.addProperty(POLICY_URI, c.getPolicyUri());
 			o.addProperty(JWKS_URI, c.getJwksUri());
 
@@ -329,12 +323,12 @@ public class ClientDetailsEntityJsonProcessor {
 			o.addProperty(TOKEN_ENDPOINT_AUTH_SIGNING_ALG, c.getTokenEndpointAuthSigningAlg() != null ? c.getTokenEndpointAuthSigningAlg().getName() : null);
 			o.addProperty(DEFAULT_MAX_AGE, c.getDefaultMaxAge());
 			o.addProperty(REQUIRE_AUTH_TIME, c.getRequireAuthTime());
-			o.add(DEFAULT_ACR_VALUES, getAsArray(c.getDefaultACRvalues()));
+			o.add(DEFAULT_ACR_VALUES, JsonUtils.getAsArray(c.getDefaultACRvalues()));
 			o.addProperty(INITIATE_LOGIN_URI, c.getInitiateLoginUri());
-			o.add(POST_LOGOUT_REDIRECT_URIS, getAsArray(c.getPostLogoutRedirectUris()));
-			o.add(REQUEST_URIS, getAsArray(c.getRequestUris()));
+			o.add(POST_LOGOUT_REDIRECT_URIS, JsonUtils.getAsArray(c.getPostLogoutRedirectUris()));
+			o.add(REQUEST_URIS, JsonUtils.getAsArray(c.getRequestUris()));
 
-			o.add(CLAIMS_REDIRECT_URIS, getAsArray(c.getClaimsRedirectUris()));
+			o.add(CLAIMS_REDIRECT_URIS, JsonUtils.getAsArray(c.getClaimsRedirectUris()));
 
 			o.addProperty(CODE_CHALLENGE_METHOD, c.getCodeChallengeMethod() != null ? c.getCodeChallengeMethod().getName() : null);
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java
index bc4f9abcd..31df218a9 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.assertion;
+package cz.muni.ics.openid.connect.assertion;
 
 import java.text.ParseException;
 import java.util.Collection;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerAuthenticationProvider.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerAuthenticationProvider.java
index 749a8eddd..4cf53dbad 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerAuthenticationProvider.java
@@ -18,19 +18,19 @@
 /**
  *
  */
-package org.mitre.openid.connect.assertion;
+package cz.muni.ics.openid.connect.assertion;
 
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.jwt.signer.service.impl.ClientKeyCacheService;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
 import java.text.ParseException;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AuthMethod;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java
index e1e5ca906..fa251833d 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.assertion;
+package cz.muni.ics.openid.connect.assertion;
 
 import java.io.IOException;
 import java.text.ParseException;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/ConfigurationBeanLocaleResolver.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationBeanLocaleResolver.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/config/ConfigurationBeanLocaleResolver.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationBeanLocaleResolver.java
index 5435f8d39..aa89bce52 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/ConfigurationBeanLocaleResolver.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationBeanLocaleResolver.java
@@ -16,7 +16,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import java.util.Locale;
 import java.util.TimeZone;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBean.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBean.java
index 7e06e2a7a..b98b32820 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBean.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import java.util.List;
 import java.util.Locale;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JWKSetEditor.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/JWKSetEditor.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/config/JWKSetEditor.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/JWKSetEditor.java
index 36d9246d2..43cce8638 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JWKSetEditor.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/JWKSetEditor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import java.beans.PropertyEditorSupport;
 import java.text.ParseException;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/JsonMessageSource.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/JsonMessageSource.java
index 10746d3dd..03526b008 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/JsonMessageSource.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import java.io.File;
 import java.io.FileNotFoundException;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ServerConfiguration.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ServerConfiguration.java
index 54b173ae5..51097ba6f 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/ServerConfiguration.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import java.util.List;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/UIConfiguration.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/UIConfiguration.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/config/UIConfiguration.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/UIConfiguration.java
index d4097cfbf..57f4d4384 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/UIConfiguration.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/config/UIConfiguration.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import java.util.Set;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/exception/ValidationException.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/exception/ValidationException.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/exception/ValidationException.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/exception/ValidationException.java
index 85efe2c87..17336639d 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/exception/ValidationException.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/exception/ValidationException.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.exception;
+package cz.muni.ics.openid.connect.exception;
 
 import org.springframework.http.HttpStatus;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/AuthorizationRequestFilter.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/filter/AuthorizationRequestFilter.java
similarity index 81%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/filter/AuthorizationRequestFilter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/filter/AuthorizationRequestFilter.java
index 63e239053..a8b9eba39 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/AuthorizationRequestFilter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/filter/AuthorizationRequestFilter.java
@@ -18,18 +18,13 @@
 /**
  *
  */
-package org.mitre.openid.connect.filter;
-
-import static org.mitre.openid.connect.request.ConnectRequestParameters.ERROR;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_HINT;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_REQUIRED;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_LOGIN;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_NONE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.STATE;
+package cz.muni.ics.openid.connect.filter;
 
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.request.ConnectRequestParameters;
+import cz.muni.ics.openid.connect.service.LoginHintExtracter;
+import cz.muni.ics.openid.connect.service.impl.RemoveLoginHintsWithHTTP;
+import cz.muni.ics.openid.connect.web.AuthenticationTimeStamper;
 import java.io.IOException;
 import java.net.URISyntaxException;
 import java.util.Date;
@@ -46,11 +41,7 @@ import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import org.apache.http.client.utils.URIBuilder;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.service.LoginHintExtracter;
-import org.mitre.openid.connect.service.impl.RemoveLoginHintsWithHTTP;
-import org.mitre.openid.connect.web.AuthenticationTimeStamper;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -126,19 +117,19 @@ public class AuthorizationRequestFilter extends GenericFilterBean {
 
 			// save the login hint to the session
 			// but first check to see if the login hint makes any sense
-			String loginHint = loginHintExtracter.extractHint((String) authRequest.getExtensions().get(LOGIN_HINT));
+			String loginHint = loginHintExtracter.extractHint((String) authRequest.getExtensions().get(ConnectRequestParameters.LOGIN_HINT));
 			if (!Strings.isNullOrEmpty(loginHint)) {
-				session.setAttribute(LOGIN_HINT, loginHint);
+				session.setAttribute(ConnectRequestParameters.LOGIN_HINT, loginHint);
 			} else {
-				session.removeAttribute(LOGIN_HINT);
+				session.removeAttribute(ConnectRequestParameters.LOGIN_HINT);
 			}
 
-			if (authRequest.getExtensions().get(PROMPT) != null) {
+			if (authRequest.getExtensions().get(ConnectRequestParameters.PROMPT) != null) {
 				// we have a "prompt" parameter
-				String prompt = (String)authRequest.getExtensions().get(PROMPT);
-				List<String> prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
+				String prompt = (String)authRequest.getExtensions().get(ConnectRequestParameters.PROMPT);
+				List<String> prompts = Splitter.on(ConnectRequestParameters.PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
 
-				if (prompts.contains(PROMPT_NONE)) {
+				if (prompts.contains(ConnectRequestParameters.PROMPT_NONE)) {
 					// see if the user's logged in
 					Authentication auth = SecurityContextHolder.getContext().getAuthentication();
 
@@ -158,9 +149,9 @@ public class AuthorizationRequestFilter extends GenericFilterBean {
 							try {
 								URIBuilder uriBuilder = new URIBuilder(url);
 
-								uriBuilder.addParameter(ERROR, LOGIN_REQUIRED);
+								uriBuilder.addParameter(ConnectRequestParameters.ERROR, ConnectRequestParameters.LOGIN_REQUIRED);
 								if (!Strings.isNullOrEmpty(authRequest.getState())) {
-									uriBuilder.addParameter(STATE, authRequest.getState()); // copy the state parameter if one was given
+									uriBuilder.addParameter(ConnectRequestParameters.STATE, authRequest.getState()); // copy the state parameter if one was given
 								}
 
 								response.sendRedirect(uriBuilder.toString());
@@ -176,7 +167,7 @@ public class AuthorizationRequestFilter extends GenericFilterBean {
 						response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
 						return;
 					}
-				} else if (prompts.contains(PROMPT_LOGIN)) {
+				} else if (prompts.contains(ConnectRequestParameters.PROMPT_LOGIN)) {
 
 					// first see if the user's already been prompted in this session
 					if (session.getAttribute(PROMPTED) == null) {
@@ -207,12 +198,12 @@ public class AuthorizationRequestFilter extends GenericFilterBean {
 					chain.doFilter(req, res);
 				}
 
-			} else if (authRequest.getExtensions().get(MAX_AGE) != null ||
+			} else if (authRequest.getExtensions().get(ConnectRequestParameters.MAX_AGE) != null ||
 					(client != null && client.getDefaultMaxAge() != null)) {
 
 				// default to the client's stored value, check the string parameter
 				Integer max = (client != null ? client.getDefaultMaxAge() : null);
-				String maxAge = (String) authRequest.getExtensions().get(MAX_AGE);
+				String maxAge = (String) authRequest.getExtensions().get(ConnectRequestParameters.MAX_AGE);
 				if (maxAge != null) {
 					max = Integer.parseInt(maxAge);
 				}
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/MultiUrlRequestMatcher.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/filter/MultiUrlRequestMatcher.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/filter/MultiUrlRequestMatcher.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/filter/MultiUrlRequestMatcher.java
index 4f004dedf..d11d20d13 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/MultiUrlRequestMatcher.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/filter/MultiUrlRequestMatcher.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.filter;
+package cz.muni.ics.openid.connect.filter;
 
 import static org.springframework.web.bind.annotation.RequestMethod.OPTIONS;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/Address.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/Address.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/Address.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/Address.java
index 40ff8820f..bf8a1f1b2 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/Address.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/Address.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
 import java.io.Serializable;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/ApprovedSite.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/ApprovedSite.java
index d3f868ee4..2d710e03a 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/ApprovedSite.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
 import java.util.Date;
 import java.util.Set;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/BlacklistedSite.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/BlacklistedSite.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/BlacklistedSite.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/BlacklistedSite.java
index 76531b5e4..defc96c76 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/BlacklistedSite.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/BlacklistedSite.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
 import javax.persistence.Basic;
 import javax.persistence.Column;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/DefaultAddress.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/DefaultAddress.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/DefaultAddress.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/DefaultAddress.java
index 4a4cb5e8d..f63262f32 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/DefaultAddress.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/DefaultAddress.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
 import javax.persistence.Basic;
 import javax.persistence.Column;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/DefaultUserInfo.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/DefaultUserInfo.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/DefaultUserInfo.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/DefaultUserInfo.java
index da152546d..2265d5b59 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/DefaultUserInfo.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/DefaultUserInfo.java
@@ -15,8 +15,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
+import cz.muni.ics.openid.connect.model.convert.JsonObjectStringConverter;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
@@ -35,8 +36,6 @@ import javax.persistence.NamedQuery;
 import javax.persistence.OneToOne;
 import javax.persistence.Table;
 
-import org.mitre.openid.connect.model.convert.JsonObjectStringConverter;
-
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/OIDCAuthenticationToken.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/OIDCAuthenticationToken.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/OIDCAuthenticationToken.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/OIDCAuthenticationToken.java
index 56cea6959..7b5884761 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/OIDCAuthenticationToken.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/OIDCAuthenticationToken.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
 import java.io.IOException;
 import java.io.ObjectInputStream;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/PairwiseIdentifier.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/PairwiseIdentifier.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/PairwiseIdentifier.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/PairwiseIdentifier.java
index 31bf8707b..a7f1a79b2 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/PairwiseIdentifier.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/PairwiseIdentifier.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
 import javax.persistence.Basic;
 import javax.persistence.Column;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/PendingOIDCAuthenticationToken.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/PendingOIDCAuthenticationToken.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/PendingOIDCAuthenticationToken.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/PendingOIDCAuthenticationToken.java
index 74af8e9e1..96fe89a2f 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/PendingOIDCAuthenticationToken.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/PendingOIDCAuthenticationToken.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
+import cz.muni.ics.openid.connect.config.ServerConfiguration;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.text.ParseException;
 import java.util.ArrayList;
 
-import org.mitre.openid.connect.config.ServerConfiguration;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.GrantedAuthority;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/UserInfo.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/UserInfo.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/UserInfo.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/UserInfo.java
index 01d1df40b..c456e975e 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/UserInfo.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/UserInfo.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
 import java.io.Serializable;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/WhitelistedSite.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/WhitelistedSite.java
index 6d6834e16..3d191939c 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/WhitelistedSite.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.model;
+package cz.muni.ics.openid.connect.model;
 
 import java.util.Set;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/convert/JsonObjectStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/convert/JsonObjectStringConverter.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/model/convert/JsonObjectStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/convert/JsonObjectStringConverter.java
index b2a0d60cd..ff485bbe4 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/model/convert/JsonObjectStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/model/convert/JsonObjectStringConverter.java
@@ -14,12 +14,11 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.model.convert;
+package cz.muni.ics.openid.connect.model.convert;
 
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
 
-import com.google.common.base.Strings;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import org.springframework.util.StringUtils;
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/models/Acr.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/models/Acr.java
new file mode 100644
index 000000000..9ec309e93
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/models/Acr.java
@@ -0,0 +1,137 @@
+package cz.muni.ics.openid.connect.models;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+import static cz.muni.ics.openid.connect.models.Acr.PARAM_EXPIRES_AT;
+import static cz.muni.ics.openid.connect.models.Acr.PARAM_SUB;
+
+/**
+ * Model of ACR.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Entity
+@Table(name = "acrs")
+@NamedQueries({
+	@NamedQuery(name = Acr.GET_ACTIVE, query = "SELECT acr FROM Acr acr WHERE " +
+		"acr.sub = :" + PARAM_SUB +
+		" AND acr.clientId = :" + Acr.PARAM_CLIENT_ID +
+		" AND acr.state = :" + Acr.PARAM_STATE +
+		" AND acr.expiresAt > :" + PARAM_EXPIRES_AT),
+	@NamedQuery(name = Acr.GET_BY_ID,
+		query = "SELECT acr FROM Acr acr " +
+			"WHERE acr.id = :" + Acr.PARAM_ID +
+			" AND acr.expiresAt > :" + PARAM_EXPIRES_AT),
+	@NamedQuery(name = Acr.DELETE_EXPIRED,
+		query = "DELETE FROM Acr acr WHERE acr.expiresAt <= :" + Acr.PARAM_EXPIRES_AT)
+})
+public class Acr {
+
+	public static final String GET_ACTIVE = "Acr.getActive";
+	public static final String GET_BY_ID = "Acr.getById";
+	public static final String DELETE_EXPIRED = "Acr.deleteExpired";
+
+	public static final String PARAM_ID = "id";
+	public static final String PARAM_SUB = "sub";
+	public static final String PARAM_CLIENT_ID = "client_id";
+	public static final String PARAM_STATE = "state";
+	public static final String PARAM_EXPIRES_AT = "expiration";
+
+	private Long id;
+	private String sub;
+	private String clientId;
+	private String state;
+	private String shibAuthnContextClass;
+	private long expiresAt;
+
+	public Acr() { }
+
+	public Acr(String sub, String clientId, String state, String shibAuthnContextClass, long expiresAt) {
+		this.sub = sub;
+		this.clientId = clientId;
+		this.state = state;
+		this.shibAuthnContextClass = shibAuthnContextClass;
+		this.expiresAt = expiresAt;
+	}
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name = "id")
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	@Basic
+	@Column(name = "sub")
+	public String getSub() {
+		return sub;
+	}
+
+	public void setSub(String sub) {
+		this.sub = sub;
+	}
+
+	@Basic
+	@Column(name = "client_id")
+	public String getClientId() {
+		return clientId;
+	}
+
+	public void setClientId(String clientId) {
+		this.clientId = clientId;
+	}
+
+	@Basic
+	@Column(name = "state")
+	public String getState() {
+		return state;
+	}
+
+	public void setState(String state) {
+		this.state = state;
+	}
+
+	@Basic
+	@Column(name = "shib_authn_context_class")
+	public String getShibAuthnContextClass() {
+		return shibAuthnContextClass;
+	}
+
+	public void setShibAuthnContextClass(String shibAuthnContextClass) {
+		this.shibAuthnContextClass = shibAuthnContextClass;
+	}
+
+	@Basic
+	@Column(name = "expiration")
+	public long getExpiresAt() {
+		return expiresAt;
+	}
+
+	public void setExpiresAt(long expiresAt) {
+		this.expiresAt = expiresAt;
+	}
+
+	@Override
+	public String toString() {
+		return "Acr{" +
+			"id=" + id +
+			", sub='" + sub + '\'' +
+			", clientId='" + clientId + '\'' +
+			", state='" + state + '\'' +
+			", shibAuthnContextClass='" + shibAuthnContextClass + '\'' +
+			", expiration=" + expiresAt +
+			'}';
+	}
+}
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/models/DeviceCodeAcr.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/models/DeviceCodeAcr.java
new file mode 100644
index 000000000..2c528c3f7
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/models/DeviceCodeAcr.java
@@ -0,0 +1,122 @@
+package cz.muni.ics.openid.connect.models;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * Model of ACR for device_code flow.
+ *
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Entity
+@Table(name = "device_code_acrs")
+@NamedQueries({
+	@NamedQuery(name = DeviceCodeAcr.GET_ACTIVE_BY_DEVICE_CODE,
+		query = "SELECT acr FROM DeviceCodeAcr acr WHERE " +
+			"acr.deviceCode = :" + DeviceCodeAcr.PARAM_DEVICE_CODE +
+			" AND acr.expiresAt > :" + DeviceCodeAcr.PARAM_EXPIRES_AT),
+	@NamedQuery(name = DeviceCodeAcr.GET_BY_ID,
+		query = "SELECT acr FROM DeviceCodeAcr acr " +
+			"WHERE acr.id = :" + DeviceCodeAcr.PARAM_ID +
+			" AND acr.expiresAt > :" + DeviceCodeAcr.PARAM_EXPIRES_AT),
+	@NamedQuery(name = DeviceCodeAcr.GET_BY_USER_CODE,
+		query = "SELECT acr FROM DeviceCodeAcr acr " +
+			"WHERE acr.userCode = :" + DeviceCodeAcr.PARAM_USER_CODE),
+	@NamedQuery(name = DeviceCodeAcr.DELETE_EXPIRED,
+		query = "DELETE FROM DeviceCodeAcr acr WHERE acr.expiresAt <= :" + DeviceCodeAcr.PARAM_EXPIRES_AT)
+})
+public class DeviceCodeAcr {
+
+	public static final String GET_ACTIVE_BY_DEVICE_CODE = "DeviceCodeAcr.getActive";
+	public static final String GET_BY_ID = "DeviceCodeAcr.getById";
+	public static final String DELETE_EXPIRED = "DeviceCodeAcr.deleteExpired";
+	public static final String GET_BY_USER_CODE = "DeviceCodeAcr.getByUserCode";
+
+	public static final String PARAM_ID = "id";
+	public static final String PARAM_USER_CODE = "user_code";
+	public static final String PARAM_DEVICE_CODE = "device_code";
+	public static final String PARAM_EXPIRES_AT = "expiration";
+
+	private Long id;
+	private String userCode;
+	private String deviceCode;
+	private String shibAuthnContextClass;
+	private long expiresAt;
+
+	public DeviceCodeAcr() { }
+
+	public DeviceCodeAcr(String deviceCode, String userCode) {
+		this.deviceCode = deviceCode;
+		this.userCode = userCode;
+	}
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name = "id")
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	@Basic
+	@Column(name = "device_code")
+	public String getDeviceCode() {
+		return deviceCode;
+	}
+
+	public void setDeviceCode(String deviceCode) {
+		this.deviceCode = deviceCode;
+	}
+
+	@Basic
+	@Column(name = "user_code")
+	public String getUserCode() {
+		return userCode;
+	}
+
+	public void setUserCode(String userCode) {
+		this.userCode = userCode;
+	}
+
+	@Basic
+	@Column(name = "shib_authn_context_class")
+	public String getShibAuthnContextClass() {
+		return shibAuthnContextClass;
+	}
+
+	public void setShibAuthnContextClass(String shibAuthnContextClass) {
+		this.shibAuthnContextClass = shibAuthnContextClass;
+	}
+
+	@Basic
+	@Column(name = "expiration")
+	public long getExpiresAt() {
+		return expiresAt;
+	}
+
+	public void setExpiresAt(long expiresAt) {
+		this.expiresAt = expiresAt;
+	}
+
+	@Override
+	public String toString() {
+		return "Acr{" +
+			"id=" + id +
+			", deviceCode='" + deviceCode + '\'' +
+			", userCode='" + userCode + '\'' +
+			", shibAuthnContextClass='" + shibAuthnContextClass + '\'' +
+			", expiration=" + expiresAt +
+			'}';
+	}
+
+}
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/AddressRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/AddressRepository.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/AddressRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/AddressRepository.java
index 16c6f48cb..77b6ed6f3 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/AddressRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/AddressRepository.java
@@ -15,9 +15,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.repository;
+package cz.muni.ics.openid.connect.repository;
 
-import org.mitre.openid.connect.model.Address;
+import cz.muni.ics.openid.connect.model.Address;
 
 /**
  * Address repository interface
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/ApprovedSiteRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/ApprovedSiteRepository.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/ApprovedSiteRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/ApprovedSiteRepository.java
index 65b20d54b..b64efc570 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/ApprovedSiteRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/ApprovedSiteRepository.java
@@ -15,11 +15,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.repository;
+package cz.muni.ics.openid.connect.repository;
 
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.ApprovedSite;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
 
 /**
  * ApprovedSite repository interface
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/BlacklistedSiteRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/BlacklistedSiteRepository.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/BlacklistedSiteRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/BlacklistedSiteRepository.java
index ec0b332c0..7f94d2887 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/BlacklistedSiteRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/BlacklistedSiteRepository.java
@@ -18,11 +18,11 @@
 /**
  *
  */
-package org.mitre.openid.connect.repository;
+package cz.muni.ics.openid.connect.repository;
 
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.BlacklistedSite;
+import cz.muni.ics.openid.connect.model.BlacklistedSite;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/PairwiseIdentifierRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/PairwiseIdentifierRepository.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/PairwiseIdentifierRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/PairwiseIdentifierRepository.java
index 930164829..9a7ea6b81 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/PairwiseIdentifierRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/PairwiseIdentifierRepository.java
@@ -18,9 +18,9 @@
 /**
  *
  */
-package org.mitre.openid.connect.repository;
+package cz.muni.ics.openid.connect.repository;
 
-import org.mitre.openid.connect.model.PairwiseIdentifier;
+import cz.muni.ics.openid.connect.model.PairwiseIdentifier;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/UserInfoRepository.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/UserInfoRepository.java
index 8dbc4714d..67167d9fa 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/UserInfoRepository.java
@@ -15,9 +15,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.repository;
+package cz.muni.ics.openid.connect.repository;
 
-import org.mitre.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.model.UserInfo;
 
 /**
  * UserInfo repository interface
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/WhitelistedSiteRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/WhitelistedSiteRepository.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/WhitelistedSiteRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/WhitelistedSiteRepository.java
index e7aee372e..616584acd 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/WhitelistedSiteRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/WhitelistedSiteRepository.java
@@ -15,11 +15,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.repository;
+package cz.muni.ics.openid.connect.repository;
 
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
 
 /**
  * WhitelistedSite repository interface
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaAddressRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaAddressRepository.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaAddressRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaAddressRepository.java
index c2556d62f..fe5fbed49 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaAddressRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaAddressRepository.java
@@ -15,13 +15,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.repository.impl;
+package cz.muni.ics.openid.connect.repository.impl;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 
-import org.mitre.openid.connect.model.Address;
-import org.mitre.openid.connect.repository.AddressRepository;
+import cz.muni.ics.openid.connect.model.Address;
+import cz.muni.ics.openid.connect.repository.AddressRepository;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaApprovedSiteRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaApprovedSiteRepository.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaApprovedSiteRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaApprovedSiteRepository.java
index fd8c34108..a9a02b56d 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaApprovedSiteRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaApprovedSiteRepository.java
@@ -15,18 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.repository.impl;
-
-import static org.mitre.util.jpa.JpaUtil.saveOrUpdate;
+package cz.muni.ics.openid.connect.repository.impl;
 
+import cz.muni.ics.util.jpa.JpaUtil;
 import java.util.Collection;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.openid.connect.repository.ApprovedSiteRepository;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.openid.connect.repository.ApprovedSiteRepository;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -70,7 +69,7 @@ public class JpaApprovedSiteRepository implements ApprovedSiteRepository {
 	@Override
 	@Transactional(value="defaultTransactionManager")
 	public ApprovedSite save(ApprovedSite approvedSite) {
-		return saveOrUpdate(manager, approvedSite);
+		return JpaUtil.saveOrUpdate(manager, approvedSite);
 	}
 
 	@Override
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java
similarity index 72%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java
index 643bf9b8b..c2c38ce9f 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java
@@ -18,18 +18,17 @@
 /**
  *
  */
-package org.mitre.openid.connect.repository.impl;
-
-import static org.mitre.util.jpa.JpaUtil.saveOrUpdate;
+package cz.muni.ics.openid.connect.repository.impl;
 
+import cz.muni.ics.util.jpa.JpaUtil;
 import java.util.Collection;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.openid.connect.model.BlacklistedSite;
-import org.mitre.openid.connect.repository.BlacklistedSiteRepository;
+import cz.muni.ics.openid.connect.model.BlacklistedSite;
+import cz.muni.ics.openid.connect.repository.BlacklistedSiteRepository;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -44,7 +43,7 @@ public class JpaBlacklistedSiteRepository implements BlacklistedSiteRepository {
 	private EntityManager manager;
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#getAll()
+	 * @see cz.muni.ics.openid.connect.repository.BlacklistedSiteRepository#getAll()
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -54,7 +53,7 @@ public class JpaBlacklistedSiteRepository implements BlacklistedSiteRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#getById(java.lang.Long)
+	 * @see cz.muni.ics.openid.connect.repository.BlacklistedSiteRepository#getById(java.lang.Long)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -63,7 +62,7 @@ public class JpaBlacklistedSiteRepository implements BlacklistedSiteRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#remove(org.mitre.openid.connect.model.BlacklistedSite)
+	 * @see cz.muni.ics.openid.connect.repository.BlacklistedSiteRepository#remove(cz.muni.ics.openid.connect.model.BlacklistedSite)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
@@ -79,23 +78,23 @@ public class JpaBlacklistedSiteRepository implements BlacklistedSiteRepository {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#save(org.mitre.openid.connect.model.BlacklistedSite)
+	 * @see cz.muni.ics.openid.connect.repository.BlacklistedSiteRepository#save(cz.muni.ics.openid.connect.model.BlacklistedSite)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
 	public BlacklistedSite save(BlacklistedSite blacklistedSite) {
-		return saveOrUpdate(manager, blacklistedSite);
+		return JpaUtil.saveOrUpdate(manager, blacklistedSite);
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#update(org.mitre.openid.connect.model.BlacklistedSite, org.mitre.openid.connect.model.BlacklistedSite)
+	 * @see cz.muni.ics.openid.connect.repository.BlacklistedSiteRepository#update(cz.muni.ics.openid.connect.model.BlacklistedSite, cz.muni.ics.openid.connect.model.BlacklistedSite)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
 	public BlacklistedSite update(BlacklistedSite oldBlacklistedSite, BlacklistedSite blacklistedSite) {
 
 		blacklistedSite.setId(oldBlacklistedSite.getId());
-		return saveOrUpdate(manager, blacklistedSite);
+		return JpaUtil.saveOrUpdate(manager, blacklistedSite);
 
 	}
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java
similarity index 74%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java
index 4289d51a7..2be01e029 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java
@@ -18,17 +18,15 @@
 /**
  *
  */
-package org.mitre.openid.connect.repository.impl;
-
-import static org.mitre.util.jpa.JpaUtil.getSingleResult;
-import static org.mitre.util.jpa.JpaUtil.saveOrUpdate;
+package cz.muni.ics.openid.connect.repository.impl;
 
+import cz.muni.ics.util.jpa.JpaUtil;
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.openid.connect.model.PairwiseIdentifier;
-import org.mitre.openid.connect.repository.PairwiseIdentifierRepository;
+import cz.muni.ics.openid.connect.model.PairwiseIdentifier;
+import cz.muni.ics.openid.connect.repository.PairwiseIdentifierRepository;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -43,7 +41,7 @@ public class JpaPairwiseIdentifierRepository implements PairwiseIdentifierReposi
 	private EntityManager manager;
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.repository.PairwiseIdentifierRepository#getBySectorIdentifier(java.lang.String, java.lang.String)
+	 * @see cz.muni.ics.openid.connect.repository.PairwiseIdentifierRepository#getBySectorIdentifier(java.lang.String, java.lang.String)
 	 */
 	@Override
 	public PairwiseIdentifier getBySectorIdentifier(String sub, String sectorIdentifierUri) {
@@ -51,16 +49,16 @@ public class JpaPairwiseIdentifierRepository implements PairwiseIdentifierReposi
 		query.setParameter(PairwiseIdentifier.PARAM_SUB, sub);
 		query.setParameter(PairwiseIdentifier.PARAM_SECTOR_IDENTIFIER, sectorIdentifierUri);
 
-		return getSingleResult(query.getResultList());
+		return JpaUtil.getSingleResult(query.getResultList());
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.repository.PairwiseIdentifierRepository#save(org.mitre.openid.connect.model.PairwiseIdentifier)
+	 * @see cz.muni.ics.openid.connect.repository.PairwiseIdentifierRepository#save(cz.muni.ics.openid.connect.model.PairwiseIdentifier)
 	 */
 	@Override
 	@Transactional(value="defaultTransactionManager")
 	public void save(PairwiseIdentifier pairwise) {
-		saveOrUpdate(manager, pairwise);
+		JpaUtil.saveOrUpdate(manager, pairwise);
 	}
 
 }
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaUserInfoRepository.java
similarity index 83%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaUserInfoRepository.java
index 7627246da..8ab345473 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaUserInfoRepository.java
@@ -15,17 +15,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.repository.impl;
-
-import static org.mitre.util.jpa.JpaUtil.getSingleResult;
+package cz.muni.ics.openid.connect.repository.impl;
 
+import cz.muni.ics.util.jpa.JpaUtil;
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.openid.connect.model.DefaultUserInfo;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.repository.UserInfoRepository;
+import cz.muni.ics.openid.connect.model.DefaultUserInfo;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.repository.UserInfoRepository;
 import org.springframework.stereotype.Repository;
 
 /**
@@ -48,7 +47,7 @@ public class JpaUserInfoRepository implements UserInfoRepository {
 		TypedQuery<DefaultUserInfo> query = manager.createNamedQuery(DefaultUserInfo.QUERY_BY_USERNAME, DefaultUserInfo.class);
 		query.setParameter(DefaultUserInfo.PARAM_USERNAME, username);
 
-		return getSingleResult(query.getResultList());
+		return JpaUtil.getSingleResult(query.getResultList());
 
 	}
 
@@ -60,7 +59,7 @@ public class JpaUserInfoRepository implements UserInfoRepository {
 		TypedQuery<DefaultUserInfo> query = manager.createNamedQuery(DefaultUserInfo.QUERY_BY_EMAIL, DefaultUserInfo.class);
 		query.setParameter(DefaultUserInfo.PARAM_EMAIL, email);
 
-		return getSingleResult(query.getResultList());
+		return JpaUtil.getSingleResult(query.getResultList());
 	}
 
 }
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java
index 9f8e57c09..044f65ae5 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java
@@ -15,19 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.repository.impl;
-
-import static org.mitre.util.jpa.JpaUtil.saveOrUpdate;
+package cz.muni.ics.openid.connect.repository.impl;
 
+import cz.muni.ics.util.jpa.JpaUtil;
 import java.util.Collection;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.TypedQuery;
 
-import org.mitre.openid.connect.model.WhitelistedSite;
-import org.mitre.openid.connect.repository.WhitelistedSiteRepository;
-import org.mitre.util.jpa.JpaUtil;
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.repository.WhitelistedSiteRepository;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -71,7 +69,7 @@ public class JpaWhitelistedSiteRepository implements WhitelistedSiteRepository {
 	@Override
 	@Transactional(value="defaultTransactionManager")
 	public WhitelistedSite save(WhitelistedSite whiteListedSite) {
-		return saveOrUpdate(manager, whiteListedSite);
+		return JpaUtil.saveOrUpdate(manager, whiteListedSite);
 	}
 
 	@Override
@@ -80,7 +78,7 @@ public class JpaWhitelistedSiteRepository implements WhitelistedSiteRepository {
 		// sanity check
 		whitelistedSite.setId(oldWhitelistedSite.getId());
 
-		return saveOrUpdate(manager, whitelistedSite);
+		return JpaUtil.saveOrUpdate(manager, whitelistedSite);
 	}
 
 	@Override
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectOAuth2RequestFactory.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/request/ConnectOAuth2RequestFactory.java
similarity index 71%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectOAuth2RequestFactory.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/request/ConnectOAuth2RequestFactory.java
index d957f31a4..561bbbe1f 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectOAuth2RequestFactory.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/request/ConnectOAuth2RequestFactory.java
@@ -15,37 +15,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.request;
+package cz.muni.ics.openid.connect.request;
 
 
-import static org.mitre.openid.connect.request.ConnectRequestParameters.AUD;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.CLAIMS;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.CLIENT_ID;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE_METHOD;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.DISPLAY;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_HINT;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.NONCE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.REDIRECT_URI;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.REQUEST;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.RESPONSE_TYPE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.SCOPE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.STATE;
-
+import cz.muni.ics.jwt.encryption.service.JWTEncryptionAndDecryptionService;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.jwt.signer.service.impl.ClientKeyCacheService;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
 import java.io.Serializable;
 import java.text.ParseException;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
 
-import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.PKCEAlgorithm;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.PKCEAlgorithm;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -112,46 +96,46 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 
 		//Add extension parameters to the 'extensions' map
 
-		if (inputParams.containsKey(PROMPT)) {
-			request.getExtensions().put(PROMPT, inputParams.get(PROMPT));
+		if (inputParams.containsKey(ConnectRequestParameters.PROMPT)) {
+			request.getExtensions().put(ConnectRequestParameters.PROMPT, inputParams.get(ConnectRequestParameters.PROMPT));
 		}
-		if (inputParams.containsKey(NONCE)) {
-			request.getExtensions().put(NONCE, inputParams.get(NONCE));
+		if (inputParams.containsKey(ConnectRequestParameters.NONCE)) {
+			request.getExtensions().put(ConnectRequestParameters.NONCE, inputParams.get(ConnectRequestParameters.NONCE));
 		}
 
-		if (inputParams.containsKey(CLAIMS)) {
-			JsonObject claimsRequest = parseClaimRequest(inputParams.get(CLAIMS));
+		if (inputParams.containsKey(ConnectRequestParameters.CLAIMS)) {
+			JsonObject claimsRequest = parseClaimRequest(inputParams.get(ConnectRequestParameters.CLAIMS));
 			if (claimsRequest != null) {
-				request.getExtensions().put(CLAIMS, claimsRequest.toString());
+				request.getExtensions().put(ConnectRequestParameters.CLAIMS, claimsRequest.toString());
 			}
 		}
 
-		if (inputParams.containsKey(MAX_AGE)) {
-			request.getExtensions().put(MAX_AGE, inputParams.get(MAX_AGE));
+		if (inputParams.containsKey(ConnectRequestParameters.MAX_AGE)) {
+			request.getExtensions().put(ConnectRequestParameters.MAX_AGE, inputParams.get(ConnectRequestParameters.MAX_AGE));
 		}
 
-		if (inputParams.containsKey(LOGIN_HINT)) {
-			request.getExtensions().put(LOGIN_HINT, inputParams.get(LOGIN_HINT));
+		if (inputParams.containsKey(ConnectRequestParameters.LOGIN_HINT)) {
+			request.getExtensions().put(ConnectRequestParameters.LOGIN_HINT, inputParams.get(ConnectRequestParameters.LOGIN_HINT));
 		}
 
-		if (inputParams.containsKey(AUD)) {
-			request.getExtensions().put(AUD, inputParams.get(AUD));
+		if (inputParams.containsKey(ConnectRequestParameters.AUD)) {
+			request.getExtensions().put(ConnectRequestParameters.AUD, inputParams.get(ConnectRequestParameters.AUD));
 		}
 
-		if (inputParams.containsKey(CODE_CHALLENGE)) {
-			request.getExtensions().put(CODE_CHALLENGE, inputParams.get(CODE_CHALLENGE));
-			if (inputParams.containsKey(CODE_CHALLENGE_METHOD)) {
-				request.getExtensions().put(CODE_CHALLENGE_METHOD, inputParams.get(CODE_CHALLENGE_METHOD));
+		if (inputParams.containsKey(ConnectRequestParameters.CODE_CHALLENGE)) {
+			request.getExtensions().put(ConnectRequestParameters.CODE_CHALLENGE, inputParams.get(ConnectRequestParameters.CODE_CHALLENGE));
+			if (inputParams.containsKey(ConnectRequestParameters.CODE_CHALLENGE_METHOD)) {
+				request.getExtensions().put(ConnectRequestParameters.CODE_CHALLENGE_METHOD, inputParams.get(ConnectRequestParameters.CODE_CHALLENGE_METHOD));
 			} else {
 				// if the client doesn't specify a code challenge transformation method, it's "plain"
-				request.getExtensions().put(CODE_CHALLENGE_METHOD, PKCEAlgorithm.plain.getName());
+				request.getExtensions().put(ConnectRequestParameters.CODE_CHALLENGE_METHOD, PKCEAlgorithm.plain.getName());
 			}
 
 		}
 
-		if (inputParams.containsKey(REQUEST)) {
-			request.getExtensions().put(REQUEST, inputParams.get(REQUEST));
-			processRequestObject(inputParams.get(REQUEST), request);
+		if (inputParams.containsKey(ConnectRequestParameters.REQUEST)) {
+			request.getExtensions().put(ConnectRequestParameters.REQUEST, inputParams.get(ConnectRequestParameters.REQUEST));
+			processRequestObject(inputParams.get(ConnectRequestParameters.REQUEST), request);
 		}
 
 		if (request.getClientId() != null) {
@@ -163,8 +147,8 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 					request.setScope(clientScopes);
 				}
 
-				if (request.getExtensions().get(MAX_AGE) == null && client.getDefaultMaxAge() != null) {
-					request.getExtensions().put(MAX_AGE, client.getDefaultMaxAge().toString());
+				if (request.getExtensions().get(ConnectRequestParameters.MAX_AGE) == null && client.getDefaultMaxAge() != null) {
+					request.getExtensions().put(ConnectRequestParameters.MAX_AGE, client.getDefaultMaxAge().toString());
 				}
 			} catch (OAuth2Exception e) {
 				logger.error("Caught OAuth2 exception trying to test client scopes and max age:", e);
@@ -192,7 +176,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 
 				// need to check clientId first so that we can load the client to check other fields
 				if (request.getClientId() == null) {
-					request.setClientId(signedJwt.getJWTClaimsSet().getStringClaim(CLIENT_ID));
+					request.setClientId(signedJwt.getJWTClaimsSet().getStringClaim(ConnectRequestParameters.CLIENT_ID));
 				}
 
 				ClientDetailsEntity client = clientDetailsService.loadClientByClientId(request.getClientId());
@@ -224,7 +208,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 
 				// need to check clientId first so that we can load the client to check other fields
 				if (request.getClientId() == null) {
-					request.setClientId(plainJwt.getJWTClaimsSet().getStringClaim(CLIENT_ID));
+					request.setClientId(plainJwt.getJWTClaimsSet().getStringClaim(ConnectRequestParameters.CLIENT_ID));
 				}
 
 				ClientDetailsEntity client = clientDetailsService.loadClientByClientId(request.getClientId());
@@ -257,7 +241,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 
 				// need to check clientId first so that we can load the client to check other fields
 				if (request.getClientId() == null) {
-					request.setClientId(encryptedJWT.getJWTClaimsSet().getStringClaim(CLIENT_ID));
+					request.setClientId(encryptedJWT.getJWTClaimsSet().getStringClaim(ConnectRequestParameters.CLIENT_ID));
 				}
 
 				ClientDetailsEntity client = clientDetailsService.loadClientByClientId(request.getClientId());
@@ -278,7 +262,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 
 			JWTClaimsSet claims = jwt.getJWTClaimsSet();
 
-			Set<String> responseTypes = OAuth2Utils.parseParameterList(claims.getStringClaim(RESPONSE_TYPE));
+			Set<String> responseTypes = OAuth2Utils.parseParameterList(claims.getStringClaim(ConnectRequestParameters.RESPONSE_TYPE));
 			if (!responseTypes.isEmpty()) {
 				if (!responseTypes.equals(request.getResponseTypes())) {
 					logger.info("Mismatch between request object and regular parameter for response_type, using request object");
@@ -286,7 +270,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 				request.setResponseTypes(responseTypes);
 			}
 
-			String redirectUri = claims.getStringClaim(REDIRECT_URI);
+			String redirectUri = claims.getStringClaim(ConnectRequestParameters.REDIRECT_URI);
 			if (redirectUri != null) {
 				if (!redirectUri.equals(request.getRedirectUri())) {
 					logger.info("Mismatch between request object and regular parameter for redirect_uri, using request object");
@@ -294,7 +278,7 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 				request.setRedirectUri(redirectUri);
 			}
 
-			String state = claims.getStringClaim(STATE);
+			String state = claims.getStringClaim(ConnectRequestParameters.STATE);
 			if(state != null) {
 				if (!state.equals(request.getState())) {
 					logger.info("Mismatch between request object and regular parameter for state, using request object");
@@ -302,31 +286,31 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 				request.setState(state);
 			}
 
-			String nonce = claims.getStringClaim(NONCE);
+			String nonce = claims.getStringClaim(ConnectRequestParameters.NONCE);
 			if(nonce != null) {
-				if (!nonce.equals(request.getExtensions().get(NONCE))) {
+				if (!nonce.equals(request.getExtensions().get(ConnectRequestParameters.NONCE))) {
 					logger.info("Mismatch between request object and regular parameter for nonce, using request object");
 				}
-				request.getExtensions().put(NONCE, nonce);
+				request.getExtensions().put(ConnectRequestParameters.NONCE, nonce);
 			}
 
-			String display = claims.getStringClaim(DISPLAY);
+			String display = claims.getStringClaim(ConnectRequestParameters.DISPLAY);
 			if (display != null) {
-				if (!display.equals(request.getExtensions().get(DISPLAY))) {
+				if (!display.equals(request.getExtensions().get(ConnectRequestParameters.DISPLAY))) {
 					logger.info("Mismatch between request object and regular parameter for display, using request object");
 				}
-				request.getExtensions().put(DISPLAY, display);
+				request.getExtensions().put(ConnectRequestParameters.DISPLAY, display);
 			}
 
-			String prompt = claims.getStringClaim(PROMPT);
+			String prompt = claims.getStringClaim(ConnectRequestParameters.PROMPT);
 			if (prompt != null) {
-				if (!prompt.equals(request.getExtensions().get(PROMPT))) {
+				if (!prompt.equals(request.getExtensions().get(ConnectRequestParameters.PROMPT))) {
 					logger.info("Mismatch between request object and regular parameter for prompt, using request object");
 				}
-				request.getExtensions().put(PROMPT, prompt);
+				request.getExtensions().put(ConnectRequestParameters.PROMPT, prompt);
 			}
 
-			Set<String> scope = OAuth2Utils.parseParameterList(claims.getStringClaim(SCOPE));
+			Set<String> scope = OAuth2Utils.parseParameterList(claims.getStringClaim(ConnectRequestParameters.SCOPE));
 			if (!scope.isEmpty()) {
 				if (!scope.equals(request.getScope())) {
 					logger.info("Mismatch between request object and regular parameter for scope, using request object");
@@ -334,22 +318,22 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory {
 				request.setScope(scope);
 			}
 
-			JsonObject claimRequest = parseClaimRequest(claims.getStringClaim(CLAIMS));
+			JsonObject claimRequest = parseClaimRequest(claims.getStringClaim(ConnectRequestParameters.CLAIMS));
 			if (claimRequest != null) {
-				Serializable claimExtension = request.getExtensions().get(CLAIMS);
+				Serializable claimExtension = request.getExtensions().get(ConnectRequestParameters.CLAIMS);
 				if (claimExtension == null || !claimRequest.equals(parseClaimRequest(claimExtension.toString()))) {
 					logger.info("Mismatch between request object and regular parameter for claims, using request object");
 				}
 				// we save the string because the object might not be a Java Serializable, and we can parse it easily enough anyway
-				request.getExtensions().put(CLAIMS, claimRequest.toString());
+				request.getExtensions().put(ConnectRequestParameters.CLAIMS, claimRequest.toString());
 			}
 
-			String loginHint = claims.getStringClaim(LOGIN_HINT);
+			String loginHint = claims.getStringClaim(ConnectRequestParameters.LOGIN_HINT);
 			if (loginHint != null) {
-				if (!loginHint.equals(request.getExtensions().get(LOGIN_HINT))) {
+				if (!loginHint.equals(request.getExtensions().get(ConnectRequestParameters.LOGIN_HINT))) {
 					logger.info("Mistmatch between request object and regular parameter for login_hint, using requst object");
 				}
-				request.getExtensions().put(LOGIN_HINT, loginHint);
+				request.getExtensions().put(ConnectRequestParameters.LOGIN_HINT, loginHint);
 			}
 
 		} catch (ParseException e) {
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectRequestParameters.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/request/ConnectRequestParameters.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectRequestParameters.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/request/ConnectRequestParameters.java
index cebcdd169..0a17505b6 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectRequestParameters.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/request/ConnectRequestParameters.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.request;
+package cz.muni.ics.openid.connect.request;
 
 public interface ConnectRequestParameters {
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/ApprovedSiteService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/ApprovedSiteService.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/ApprovedSiteService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/ApprovedSiteService.java
index da67a940b..12bf07d6f 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/ApprovedSiteService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/ApprovedSiteService.java
@@ -15,15 +15,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service;
+package cz.muni.ics.openid.connect.service;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
 
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.openid.connect.model.ApprovedSite;
 import org.springframework.security.oauth2.provider.ClientDetails;
 
 /**
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/BlacklistedSiteService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/BlacklistedSiteService.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/BlacklistedSiteService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/BlacklistedSiteService.java
index efe9e1846..50204a710 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/BlacklistedSiteService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/BlacklistedSiteService.java
@@ -18,11 +18,11 @@
 /**
  *
  */
-package org.mitre.openid.connect.service;
+package cz.muni.ics.openid.connect.service;
 
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.BlacklistedSite;
+import cz.muni.ics.openid.connect.model.BlacklistedSite;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/LoginHintExtracter.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/LoginHintExtracter.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/LoginHintExtracter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/LoginHintExtracter.java
index d5d8cd2ac..b9e7a4fae 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/LoginHintExtracter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/LoginHintExtracter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.service;
+package cz.muni.ics.openid.connect.service;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/OIDCTokenService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/OIDCTokenService.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/OIDCTokenService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/OIDCTokenService.java
index 55d1c0d13..aa1766245 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/OIDCTokenService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/OIDCTokenService.java
@@ -15,12 +15,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service;
+package cz.muni.ics.openid.connect.service;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
 import java.util.Date;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
 import org.springframework.security.oauth2.provider.OAuth2Request;
 
 import com.nimbusds.jwt.JWT;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/PairwiseIdentiferService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/PairwiseIdentiferService.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/PairwiseIdentiferService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/PairwiseIdentiferService.java
index 0e4747f11..563e161fc 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/PairwiseIdentiferService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/PairwiseIdentiferService.java
@@ -18,10 +18,10 @@
 /**
  *
  */
-package org.mitre.openid.connect.service;
+package cz.muni.ics.openid.connect.service;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.openid.connect.model.UserInfo;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.model.UserInfo;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/ScopeClaimTranslationService.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/ScopeClaimTranslationService.java
index 43f00f489..ffc7dd9c2 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/ScopeClaimTranslationService.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.service;
+package cz.muni.ics.openid.connect.service;
 
 import java.util.Set;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/UserInfoService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/UserInfoService.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/UserInfoService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/UserInfoService.java
index cddae299f..3f39d7cca 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/UserInfoService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/UserInfoService.java
@@ -15,9 +15,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service;
+package cz.muni.ics.openid.connect.service;
 
-import org.mitre.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.model.UserInfo;
 
 /**
  * Interface for UserInfo service
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/WhitelistedSiteService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/WhitelistedSiteService.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/WhitelistedSiteService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/WhitelistedSiteService.java
index 420fd4b24..0803febcd 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/WhitelistedSiteService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/WhitelistedSiteService.java
@@ -15,11 +15,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service;
+package cz.muni.ics.openid.connect.service;
 
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
 
 /**
  * Interface for WhitelistedSite service
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultApprovedSiteService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultApprovedSiteService.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultApprovedSiteService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultApprovedSiteService.java
index c3e91f711..1b830d24f 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultApprovedSiteService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultApprovedSiteService.java
@@ -15,15 +15,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.repository.OAuth2TokenRepository;
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.openid.connect.repository.ApprovedSiteRepository;
-import org.mitre.openid.connect.service.ApprovedSiteService;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.repository.OAuth2TokenRepository;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.openid.connect.repository.ApprovedSiteRepository;
+import cz.muni.ics.openid.connect.service.ApprovedSiteService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -117,7 +117,7 @@ public class DefaultApprovedSiteService implements ApprovedSiteService {
 	/**
 	 * @param userId
 	 * @return
-	 * @see org.mitre.openid.connect.repository.ApprovedSiteRepository#getByUserId(java.lang.String)
+	 * @see ApprovedSiteRepository#getByUserId(java.lang.String)
 	 */
 	@Override
 	public Collection<ApprovedSite> getByUserId(String userId) {
@@ -127,7 +127,7 @@ public class DefaultApprovedSiteService implements ApprovedSiteService {
 	/**
 	 * @param clientId
 	 * @return
-	 * @see org.mitre.openid.connect.repository.ApprovedSiteRepository#getByClientId(java.lang.String)
+	 * @see ApprovedSiteRepository#getByClientId(java.lang.String)
 	 */
 	@Override
 	public Collection<ApprovedSite> getByClientId(String clientId) {
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultBlacklistedSiteService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultBlacklistedSiteService.java
similarity index 72%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultBlacklistedSiteService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultBlacklistedSiteService.java
index 65652b6dc..8efa3da0a 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultBlacklistedSiteService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultBlacklistedSiteService.java
@@ -18,13 +18,13 @@
 /**
  *
  */
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.BlacklistedSite;
-import org.mitre.openid.connect.repository.BlacklistedSiteRepository;
-import org.mitre.openid.connect.service.BlacklistedSiteService;
+import cz.muni.ics.openid.connect.model.BlacklistedSite;
+import cz.muni.ics.openid.connect.repository.BlacklistedSiteRepository;
+import cz.muni.ics.openid.connect.service.BlacklistedSiteService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -43,7 +43,7 @@ public class DefaultBlacklistedSiteService implements BlacklistedSiteService {
 	private BlacklistedSiteRepository repository;
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.BlacklistedSiteService#getAll()
+	 * @see cz.muni.ics.openid.connect.service.BlacklistedSiteService#getAll()
 	 */
 	@Override
 	public Collection<BlacklistedSite> getAll() {
@@ -51,7 +51,7 @@ public class DefaultBlacklistedSiteService implements BlacklistedSiteService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.BlacklistedSiteService#getById(java.lang.Long)
+	 * @see cz.muni.ics.openid.connect.service.BlacklistedSiteService#getById(java.lang.Long)
 	 */
 	@Override
 	public BlacklistedSite getById(Long id) {
@@ -59,7 +59,7 @@ public class DefaultBlacklistedSiteService implements BlacklistedSiteService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.BlacklistedSiteService#remove(org.mitre.openid.connect.model.BlacklistedSite)
+	 * @see cz.muni.ics.openid.connect.service.BlacklistedSiteService#remove(cz.muni.ics.openid.connect.model.BlacklistedSite)
 	 */
 	@Override
 	public void remove(BlacklistedSite blacklistedSite) {
@@ -67,7 +67,7 @@ public class DefaultBlacklistedSiteService implements BlacklistedSiteService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.BlacklistedSiteService#saveNew(org.mitre.openid.connect.model.BlacklistedSite)
+	 * @see cz.muni.ics.openid.connect.service.BlacklistedSiteService#saveNew(cz.muni.ics.openid.connect.model.BlacklistedSite)
 	 */
 	@Override
 	public BlacklistedSite saveNew(BlacklistedSite blacklistedSite) {
@@ -75,7 +75,7 @@ public class DefaultBlacklistedSiteService implements BlacklistedSiteService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.BlacklistedSiteService#update(org.mitre.openid.connect.model.BlacklistedSite, org.mitre.openid.connect.model.BlacklistedSite)
+	 * @see cz.muni.ics.openid.connect.service.BlacklistedSiteService#update(cz.muni.ics.openid.connect.model.BlacklistedSite, cz.muni.ics.openid.connect.model.BlacklistedSite)
 	 */
 	@Override
 	public BlacklistedSite update(BlacklistedSite oldBlacklistedSite, BlacklistedSite blacklistedSite) {
@@ -83,7 +83,7 @@ public class DefaultBlacklistedSiteService implements BlacklistedSiteService {
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.BlacklistedSiteService#isBlacklisted(java.lang.String)
+	 * @see cz.muni.ics.openid.connect.service.BlacklistedSiteService#isBlacklisted(java.lang.String)
 	 */
 	@Override
 	public boolean isBlacklisted(String uri) {
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultOIDCTokenService.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultOIDCTokenService.java
index 2b6fb1ec7..8d345bf9a 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultOIDCTokenService.java
@@ -15,30 +15,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
-import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.NONCE;
+import static cz.muni.ics.openid.connect.request.ConnectRequestParameters.MAX_AGE;
+import static cz.muni.ics.openid.connect.request.ConnectRequestParameters.NONCE;
 
+import cz.muni.ics.jwt.encryption.service.JWTEncryptionAndDecryptionService;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.openid.connect.util.IdTokenHashUtils;
+import cz.muni.ics.openid.connect.web.AuthenticationTimeStamper;
 import java.util.Date;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
-import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
-import org.mitre.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService;
-import org.mitre.oauth2.model.AuthenticationHolderEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.repository.AuthenticationHolderRepository;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.service.OIDCTokenService;
-import org.mitre.openid.connect.util.IdTokenHashUtils;
-import org.mitre.openid.connect.web.AuthenticationTimeStamper;
+import cz.muni.ics.jwt.signer.service.impl.ClientKeyCacheService;
+import cz.muni.ics.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService;
+import cz.muni.ics.oauth2.model.AuthenticationHolderEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.repository.AuthenticationHolderRepository;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.service.OIDCTokenService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultScopeClaimTranslationService.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultScopeClaimTranslationService.java
index 5c0637a5e..eb6483bfc 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultScopeClaimTranslationService.java
@@ -15,12 +15,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
 import java.util.HashSet;
 import java.util.Set;
 
-import org.mitre.openid.connect.service.ScopeClaimTranslationService;
+import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
 import org.springframework.stereotype.Service;
 
 import com.google.common.collect.HashMultimap;
@@ -68,7 +68,7 @@ public class DefaultScopeClaimTranslationService implements ScopeClaimTranslatio
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.ScopeClaimTranslationService#getClaimsForScope(java.lang.String)
+	 * @see cz.muni.ics.openid.connect.service.ScopeClaimTranslationService#getClaimsForScope(java.lang.String)
 	 */
 	@Override
 	public Set<String> getClaimsForScope(String scope) {
@@ -80,7 +80,7 @@ public class DefaultScopeClaimTranslationService implements ScopeClaimTranslatio
 	}
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.ScopeClaimTranslationService#getClaimsForScopeSet(java.util.Set)
+	 * @see cz.muni.ics.openid.connect.service.ScopeClaimTranslationService#getClaimsForScopeSet(java.util.Set)
 	 */
 	@Override
 	public Set<String> getClaimsForScopeSet(Set<String> scopes) {
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultUserInfoService.java
similarity index 81%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultUserInfoService.java
index ce830d649..cf3c481cb 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultUserInfoService.java
@@ -15,15 +15,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.repository.UserInfoRepository;
-import org.mitre.openid.connect.service.PairwiseIdentiferService;
-import org.mitre.openid.connect.service.UserInfoService;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.SubjectType;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.repository.UserInfoRepository;
+import cz.muni.ics.openid.connect.service.PairwiseIdentiferService;
+import cz.muni.ics.openid.connect.service.UserInfoService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultWhitelistedSiteService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultWhitelistedSiteService.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultWhitelistedSiteService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultWhitelistedSiteService.java
index 3c530b513..decfa2e4e 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultWhitelistedSiteService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DefaultWhitelistedSiteService.java
@@ -15,13 +15,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.WhitelistedSite;
-import org.mitre.openid.connect.repository.WhitelistedSiteRepository;
-import org.mitre.openid.connect.service.WhitelistedSiteService;
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.repository.WhitelistedSiteRepository;
+import cz.muni.ics.openid.connect.service.WhitelistedSiteService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DummyResourceSetService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DummyResourceSetService.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DummyResourceSetService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DummyResourceSetService.java
index ad60243c1..685f3f5f4 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DummyResourceSetService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/DummyResourceSetService.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
 import java.util.Collection;
 import java.util.Collections;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.uma.model.ResourceSet;
-import org.mitre.uma.service.ResourceSetService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.uma.model.ResourceSet;
+import cz.muni.ics.uma.service.ResourceSetService;
 import org.springframework.stereotype.Service;
 
 /**
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java
similarity index 83%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java
index 74d98ea31..49bd99b69 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.LoginHintExtracter;
-import org.mitre.openid.connect.service.UserInfoService;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.LoginHintExtracter;
+import cz.muni.ics.openid.connect.service.UserInfoService;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import com.google.common.base.Strings;
@@ -34,7 +34,7 @@ public class MatchLoginHintsAgainstUsers implements LoginHintExtracter {
 	private UserInfoService userInfoService;
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.LoginHintTester#useHint(java.lang.String)
+	 * @see cz.muni.ics.openid.connect.service.LoginHintTester#useHint(java.lang.String)
 	 */
 	@Override
 	public String extractHint(String loginHint) {
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/PassAllLoginHints.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/PassAllLoginHints.java
similarity index 84%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/PassAllLoginHints.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/PassAllLoginHints.java
index b1894466b..be1ba6f13 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/PassAllLoginHints.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/PassAllLoginHints.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
-import org.mitre.openid.connect.service.LoginHintExtracter;
+import cz.muni.ics.openid.connect.service.LoginHintExtracter;
 
 /**
  * Sends all login hints through to the login page regardless of setup.
@@ -27,7 +27,7 @@ import org.mitre.openid.connect.service.LoginHintExtracter;
 public class PassAllLoginHints implements LoginHintExtracter {
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.LoginHintTester#useHint(java.lang.String)
+	 * @see cz.muni.ics.openid.connect.service.LoginHintTester#useHint(java.lang.String)
 	 */
 	@Override
 	public String extractHint(String loginHint) {
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/RemoveLoginHintsWithHTTP.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/RemoveLoginHintsWithHTTP.java
similarity index 86%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/RemoveLoginHintsWithHTTP.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/RemoveLoginHintsWithHTTP.java
index eab1585c2..ac3c84ff0 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/RemoveLoginHintsWithHTTP.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/RemoveLoginHintsWithHTTP.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
-import org.mitre.openid.connect.service.LoginHintExtracter;
+import cz.muni.ics.openid.connect.service.LoginHintExtracter;
 
 import com.google.common.base.Strings;
 
@@ -29,7 +29,7 @@ import com.google.common.base.Strings;
 public class RemoveLoginHintsWithHTTP implements LoginHintExtracter {
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.service.LoginHintTester#useHint(java.lang.String)
+	 * @see cz.muni.ics.openid.connect.service.LoginHintTester#useHint(java.lang.String)
 	 */
 	@Override
 	public String extractHint(String loginHint) {
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/UUIDPairwiseIdentiferService.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/UUIDPairwiseIdentiferService.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/UUIDPairwiseIdentiferService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/UUIDPairwiseIdentiferService.java
index 14b46fcc3..f9b1bc4f8 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/UUIDPairwiseIdentiferService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/service/impl/UUIDPairwiseIdentiferService.java
@@ -18,16 +18,16 @@
 /**
  *
  */
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
 import java.util.Set;
 import java.util.UUID;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.openid.connect.model.PairwiseIdentifier;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.repository.PairwiseIdentifierRepository;
-import org.mitre.openid.connect.service.PairwiseIdentiferService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.model.PairwiseIdentifier;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.repository.PairwiseIdentifierRepository;
+import cz.muni.ics.openid.connect.service.PairwiseIdentiferService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectTokenEnhancer.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/token/ConnectTokenEnhancer.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectTokenEnhancer.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/token/ConnectTokenEnhancer.java
index d5b6a9cdc..250b850cc 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectTokenEnhancer.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/token/ConnectTokenEnhancer.java
@@ -15,20 +15,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.token;
+package cz.muni.ics.openid.connect.token;
 
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.service.OIDCTokenService;
+import cz.muni.ics.openid.connect.service.UserInfoService;
 import java.util.Date;
 import java.util.UUID;
 
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.OIDCTokenService;
-import org.mitre.openid.connect.service.UserInfoService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.model.UserInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/token/TofuUserApprovalHandler.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/token/TofuUserApprovalHandler.java
index 821b1fafb..1d1a66d30 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/token/TofuUserApprovalHandler.java
@@ -15,13 +15,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.token;
-
-import static org.mitre.openid.connect.request.ConnectRequestParameters.APPROVED_SITE;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_CONSENT;
-import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR;
+package cz.muni.ics.openid.connect.token;
 
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.request.ConnectRequestParameters;
+import cz.muni.ics.openid.connect.service.WhitelistedSiteService;
+import cz.muni.ics.openid.connect.web.AuthenticationTimeStamper;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Date;
@@ -32,12 +31,9 @@ import java.util.Set;
 
 import javax.servlet.http.HttpSession;
 
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.openid.connect.model.WhitelistedSite;
-import org.mitre.openid.connect.service.ApprovedSiteService;
-import org.mitre.openid.connect.service.WhitelistedSiteService;
-import org.mitre.openid.connect.web.AuthenticationTimeStamper;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.openid.connect.service.ApprovedSiteService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.oauth2.provider.AuthorizationRequest;
@@ -131,9 +127,9 @@ public class TofuUserApprovalHandler implements UserApprovalHandler {
 		boolean alreadyApproved = false;
 
 		// find out if we're supposed to force a prompt on the user or not
-		String prompt = (String) authorizationRequest.getExtensions().get(PROMPT);
-		List<String> prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
-		if (!prompts.contains(PROMPT_CONSENT)) {
+		String prompt = (String) authorizationRequest.getExtensions().get(ConnectRequestParameters.PROMPT);
+		List<String> prompts = Splitter.on(ConnectRequestParameters.PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt));
+		if (!prompts.contains(ConnectRequestParameters.PROMPT_CONSENT)) {
 			// if the prompt parameter is set to "consent" then we can't use approved sites or whitelisted sites
 			// otherwise, we need to check them below
 
@@ -150,7 +146,7 @@ public class TofuUserApprovalHandler implements UserApprovalHandler {
 						approvedSiteService.save(ap);
 
 						String apId = ap.getId().toString();
-						authorizationRequest.getExtensions().put(APPROVED_SITE, apId);
+						authorizationRequest.getExtensions().put(ConnectRequestParameters.APPROVED_SITE, apId);
 						authorizationRequest.setApproved(true);
 						alreadyApproved = true;
 
@@ -227,7 +223,7 @@ public class TofuUserApprovalHandler implements UserApprovalHandler {
 
 				ApprovedSite newSite = approvedSiteService.createApprovedSite(clientId, userId, timeout, allowedScopes);
 				String newSiteId = newSite.getId().toString();
-				authorizationRequest.getExtensions().put(APPROVED_SITE, newSiteId);
+				authorizationRequest.getExtensions().put(ConnectRequestParameters.APPROVED_SITE, newSiteId);
 			}
 
 			setAuthTime(authorizationRequest);
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/util/IdTokenHashUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/util/IdTokenHashUtils.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/util/IdTokenHashUtils.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/util/IdTokenHashUtils.java
index 789f3ce18..029db1776 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/util/IdTokenHashUtils.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/util/IdTokenHashUtils.java
@@ -16,13 +16,13 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.openid.connect.util;
+package cz.muni.ics.openid.connect.util;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/AbstractClientEntityView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/AbstractClientEntityView.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/AbstractClientEntityView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/AbstractClientEntityView.java
index 719bfc8d0..f6d2be6ed 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/AbstractClientEntityView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/AbstractClientEntityView.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.io.IOException;
 import java.io.Writer;
@@ -28,7 +28,7 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.mitre.oauth2.model.PKCEAlgorithm;
+import cz.muni.ics.oauth2.model.PKCEAlgorithm;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForAdmins.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientEntityViewForAdmins.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForAdmins.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientEntityViewForAdmins.java
index 7fe86f7f7..0b1977dab 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForAdmins.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientEntityViewForAdmins.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.util.Set;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForUsers.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientEntityViewForUsers.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForUsers.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientEntityViewForUsers.java
index 11f1e51dc..3219ca4ac 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForUsers.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientEntityViewForUsers.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.util.Set;
 
@@ -46,7 +46,7 @@ public class ClientEntityViewForUsers extends AbstractClientEntityView {
 	public static final String VIEWNAME = "clientEntityViewUsers";
 
 	/* (non-Javadoc)
-	 * @see org.mitre.openid.connect.view.AbstractClientEntityView#getExclusionStrategy()
+	 * @see cz.muni.ics.openid.connect.view.AbstractClientEntityView#getExclusionStrategy()
 	 */
 	@Override
 	protected ExclusionStrategy getExclusionStrategy() {
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientInformationResponseView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientInformationResponseView.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientInformationResponseView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientInformationResponseView.java
index a4b245179..2a002199f 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientInformationResponseView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/ClientInformationResponseView.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.io.IOException;
 import java.io.Writer;
@@ -27,8 +27,8 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.mitre.oauth2.model.RegisteredClient;
-import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
+import cz.muni.ics.oauth2.model.RegisteredClient;
+import cz.muni.ics.openid.connect.ClientDetailsEntityJsonProcessor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/HttpCodeView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/HttpCodeView.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/HttpCodeView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/HttpCodeView.java
index bea53cd17..faa61975f 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/HttpCodeView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/HttpCodeView.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.util.Map;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JWKSetView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JWKSetView.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/JWKSetView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JWKSetView.java
index 351ac657f..01bdb19a3 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JWKSetView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JWKSetView.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.io.IOException;
 import java.io.Writer;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonApprovedSiteView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonApprovedSiteView.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonApprovedSiteView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonApprovedSiteView.java
index 4b84f1a8c..acd65186c 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonApprovedSiteView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonApprovedSiteView.java
@@ -18,8 +18,9 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
 import java.io.IOException;
 import java.io.Writer;
 import java.lang.reflect.Type;
@@ -28,8 +29,7 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonEntityView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonEntityView.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonEntityView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonEntityView.java
index a9e9401c6..f8afdd768 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonEntityView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonEntityView.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.io.IOException;
 import java.io.Writer;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonErrorView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonErrorView.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonErrorView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonErrorView.java
index db21fffb2..6e985fa27 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonErrorView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/JsonErrorView.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.io.IOException;
 import java.io.Writer;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoJWTView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/UserInfoJWTView.java
similarity index 91%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoJWTView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/UserInfoJWTView.java
index 1ce83dff0..f9da3e875 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoJWTView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/UserInfoJWTView.java
@@ -16,8 +16,10 @@
 /**
  *
  */
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
+import cz.muni.ics.jwt.encryption.service.JWTEncryptionAndDecryptionService;
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
@@ -29,12 +31,10 @@ import java.util.UUID;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
-import org.mitre.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.jwt.signer.service.impl.ClientKeyCacheService;
+import cz.muni.ics.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/UserInfoView.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/UserInfoView.java
index 73ca617f9..644da701b 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/view/UserInfoView.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.view;
+package cz.muni.ics.openid.connect.view;
 
 import java.io.IOException;
 import java.io.Writer;
@@ -27,8 +27,8 @@ import java.util.Set;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.ScopeClaimTranslationService;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.ScopeClaimTranslationService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ApprovedSiteAPI.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ApprovedSiteAPI.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/ApprovedSiteAPI.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ApprovedSiteAPI.java
index 1f05baab5..e5baee8c7 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ApprovedSiteAPI.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ApprovedSiteAPI.java
@@ -18,17 +18,17 @@
 /**
  *
  */
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonApprovedSiteView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
 import java.security.Principal;
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.openid.connect.service.ApprovedSiteService;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonApprovedSiteView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.openid.connect.view.JsonErrorView;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.openid.connect.service.ApprovedSiteService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/AuthenticationTimeStamper.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/AuthenticationTimeStamper.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/AuthenticationTimeStamper.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/AuthenticationTimeStamper.java
index 4b5bd4c2e..51ed441a3 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/AuthenticationTimeStamper.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/AuthenticationTimeStamper.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
 import java.io.IOException;
 import java.util.Date;
@@ -28,7 +28,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
-import org.mitre.openid.connect.filter.AuthorizationRequestFilter;
+import cz.muni.ics.openid.connect.filter.AuthorizationRequestFilter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.core.Authentication;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/BlacklistAPI.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/BlacklistAPI.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/BlacklistAPI.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/BlacklistAPI.java
index 6757df808..2d76720ab 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/BlacklistAPI.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/BlacklistAPI.java
@@ -18,16 +18,16 @@
 /**
  *
  */
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
 import java.security.Principal;
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.BlacklistedSite;
-import org.mitre.openid.connect.service.BlacklistedSiteService;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.openid.connect.view.JsonErrorView;
+import cz.muni.ics.openid.connect.model.BlacklistedSite;
+import cz.muni.ics.openid.connect.service.BlacklistedSiteService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientAPI.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ClientAPI.java
similarity index 85%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientAPI.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ClientAPI.java
index e2ccb9802..f883299b7 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientAPI.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ClientAPI.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Sets;
@@ -36,21 +36,21 @@ import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.JWTParser;
+import cz.muni.ics.jwt.assertion.AssertionValidator;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.view.ClientEntityViewForAdmins;
+import cz.muni.ics.openid.connect.view.ClientEntityViewForUsers;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
 import org.eclipse.persistence.exceptions.DatabaseException;
-import org.mitre.jwt.assertion.AssertionValidator;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
-import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
-import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
-import org.mitre.oauth2.model.PKCEAlgorithm;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.web.AuthenticationUtilities;
-import org.mitre.openid.connect.exception.ValidationException;
-import org.mitre.openid.connect.view.ClientEntityViewForAdmins;
-import org.mitre.openid.connect.view.ClientEntityViewForUsers;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.openid.connect.view.JsonErrorView;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AppType;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AuthMethod;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.SubjectType;
+import cz.muni.ics.oauth2.model.PKCEAlgorithm;
+import cz.muni.ics.oauth2.web.AuthenticationUtilities;
+import cz.muni.ics.openid.connect.exception.ValidationException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -74,43 +74,43 @@ import java.sql.SQLIntegrityConstraintViolationException;
 import java.text.ParseException;
 import java.util.Collection;
 
-import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS;
-import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
-import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
-import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.JWKS;
-import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
-import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
-import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
-import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
-import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_ID;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_NAME;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CONTACTS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.GRANT_TYPES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.JWKS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.JWKS_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.POLICY_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUEST_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SCOPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOS_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
 
 /**
  * @author Michael Jett <mjett@mitre.org>
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/DynamicClientRegistrationEndpoint.java
similarity index 88%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/DynamicClientRegistrationEndpoint.java
index 30d6159c1..cba52838d 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/DynamicClientRegistrationEndpoint.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
@@ -26,25 +26,25 @@ import com.nimbusds.jose.JWEAlgorithm;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jwt.JWTClaimsSet;
-import org.mitre.jwt.assertion.AssertionValidator;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
-import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
-import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.RegisteredClient;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.exception.ValidationException;
-import org.mitre.openid.connect.service.BlacklistedSiteService;
-import org.mitre.openid.connect.service.OIDCTokenService;
-import org.mitre.openid.connect.view.ClientInformationResponseView;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonErrorView;
+import cz.muni.ics.jwt.assertion.AssertionValidator;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.view.ClientInformationResponseView;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AppType;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AuthMethod;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.SubjectType;
+import cz.muni.ics.oauth2.model.RegisteredClient;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.ClientDetailsEntityJsonProcessor;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.exception.ValidationException;
+import cz.muni.ics.openid.connect.service.BlacklistedSiteService;
+import cz.muni.ics.openid.connect.service.OIDCTokenService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -70,43 +70,43 @@ import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
-import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
-import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS;
-import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
-import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
-import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
-import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.JWKS;
-import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
-import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS;
-import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
-import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
-import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
-import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
-import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_ID;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_NAME;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CLIENT_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.CONTACTS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.GRANT_TYPES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.JWKS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.JWKS_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.POLICY_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REDIRECT_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUEST_URIS;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.RESPONSE_TYPES;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SCOPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.SUBJECT_TYPE;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.TOS_URI;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
+import static cz.muni.ics.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
 
 @Controller
 @RequestMapping(value = DynamicClientRegistrationEndpoint.URL)
diff --git a/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/EndSessionEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/EndSessionEndpoint.java
new file mode 100644
index 000000000..32cf1ddfe
--- /dev/null
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/EndSessionEndpoint.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright 2018 The MIT Internet Trust Consortium
+ *
+ * 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 cz.muni.ics.openid.connect.web;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTParser;
+import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
+import cz.muni.ics.oidc.web.WebHtmlClasses;
+import cz.muni.ics.oidc.web.controllers.ControllerUtils;
+import cz.muni.ics.oidc.web.langs.Localization;
+import java.text.ParseException;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import cz.muni.ics.jwt.assertion.impl.SelfAssertionValidator;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
+import org.springframework.security.saml.SAMLLogoutFilter;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_POST_LOGOUT_REDIRECT_URI;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_STATE;
+import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_TARGET;
+
+/**
+ * End Session Endpoint from OIDC session management.
+ * <p>
+ * This is a copy of the original file with modification at the end of processLogout().
+ * </p>
+ *
+ * @author Martin Kuba <makub@ics.muni.cz>
+ * @author Dominik Frantisek Bucik <bucik@ics.muni.cz>
+ */
+@Controller
+//TODO: implement according to spec (https://openid.net/specs/openid-connect-rpinitiated-1_0.html)
+// other specs:
+//TODO: https://openid.net/specs/openid-connect-frontchannel-1_0.html
+//TODO: https://openid.net/specs/openid-connect-backchannel-1_0.html
+//TODO: https://openid.net/specs/openid-connect-session-1_0.html
+public class EndSessionEndpoint {
+
+	public static final String URL = "endsession";
+
+	private static final String CLIENT_KEY = "client";
+	private static final String STATE_KEY = "state";
+	private static final String REDIRECT_URI_KEY = "redirectUri";
+
+	private static final Logger logger = LoggerFactory.getLogger(EndSessionEndpoint.class);
+
+	private final SelfAssertionValidator validator;
+	private final PerunOidcConfig perunOidcConfig;
+	private final ClientDetailsEntityService clientService;
+	private final Localization localization;
+	private final WebHtmlClasses htmlClasses;
+
+	@Autowired
+	public EndSessionEndpoint(SelfAssertionValidator validator,
+							  PerunOidcConfig perunOidcConfig,
+							  ClientDetailsEntityService clientService,
+							  Localization localization,
+							  WebHtmlClasses htmlClasses) {
+		this.validator = validator;
+		this.perunOidcConfig = perunOidcConfig;
+		this.clientService = clientService;
+		this.localization = localization;
+		this.htmlClasses = htmlClasses;
+	}
+
+	@RequestMapping(value = "/" + URL, method = RequestMethod.GET)
+	public String endSession(@RequestParam(value = "id_token_hint", required = false) String idTokenHint,
+							 @RequestParam(value = PARAM_POST_LOGOUT_REDIRECT_URI, required = false) String postLogoutRedirectUri,
+							 @RequestParam(value = STATE_KEY, required = false) String state,
+							 HttpServletRequest request,
+							 HttpSession session,
+							 Authentication auth, Map<String, Object> model)
+	{
+		JWTClaimsSet idTokenClaims = null; // pulled from the parsed and validated ID token
+		ClientDetailsEntity client = null; // pulled from ID token's audience field
+
+		if (!Strings.isNullOrEmpty(postLogoutRedirectUri)) {
+			session.setAttribute(REDIRECT_URI_KEY, postLogoutRedirectUri);
+		}
+		if (!Strings.isNullOrEmpty(state)) {
+			session.setAttribute(STATE_KEY, state);
+		}
+
+		// parse the ID token hint to see if it's valid
+		if (!Strings.isNullOrEmpty(idTokenHint)) {
+			try {
+				JWT idToken = JWTParser.parse(idTokenHint);
+
+				if (validator.isValid(idToken)) {
+					// we issued this ID token, figure out who it's for
+					idTokenClaims = idToken.getJWTClaimsSet();
+
+					String clientId = Iterables.getOnlyElement(idTokenClaims.getAudience());
+
+					client = clientService.loadClientByClientId(clientId);
+
+					// save a reference in the session for us to pick up later
+					//session.setAttribute("endSession_idTokenHint_claims", idTokenClaims);
+					session.setAttribute(CLIENT_KEY, client);
+				}
+			} catch (ParseException e) {
+				// it's not a valid ID token, ignore it
+				logger.debug("Invalid id token hint", e);
+			} catch (InvalidClientException e) {
+				// couldn't find the client, ignore it
+				logger.debug("Invalid client", e);
+			}
+		}
+
+		// are we logged in or not?
+		if (auth == null || !request.isUserInRole("ROLE_USER")) {
+			// we're not logged in anyway, process the final redirect bits if needed
+			return processLogout(null, null, request, session);
+		} else {
+			logger.info("Logout confirmating for user {} from client {}", auth.getName(), client != null ? client.getClientName() : "unknown");
+			// we are logged in, need to prompt the user before we log out
+			model.put("client", client);
+			model.put("idToken", idTokenClaims);
+
+			ControllerUtils.setPageOptions(model, request, localization, htmlClasses, perunOidcConfig);
+
+			// display the log out confirmation page
+			return "logout";
+		}
+	}
+
+	@RequestMapping(value = "/" + URL, method = RequestMethod.POST)
+	public String processLogout(@RequestParam(value = "approve", required = false) String approved,
+								@RequestParam(value = "deny", required = false) String deny,
+								HttpServletRequest request,
+								HttpSession session)
+	{
+		String redirectUri = (String) session.getAttribute(REDIRECT_URI_KEY);
+		String state = (String) session.getAttribute(STATE_KEY);
+		ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(CLIENT_KEY);
+		String redirectURL = null;
+
+		// if we have a client AND the client has post-logout redirect URIs
+		// registered AND the URI given is in that list, then...
+		if (isUriValid(redirectUri, client)) {
+			UriComponentsBuilder uri = UriComponentsBuilder.fromHttpUrl(redirectUri);
+			if (StringUtils.hasText(state)) {
+				uri = uri.queryParam("state", state);
+			}
+			UriComponents uriComponents = uri.build();
+			logger.trace("redirect URL: {}", uriComponents);
+			redirectURL = uriComponents.toString();
+		}
+
+		if (redirectURL != null) {
+			String target = getRedirectUrl(redirectUri, state);
+			if (StringUtils.hasText(approved)) {
+				target = getLogoutUrl(target);
+				logger.trace("redirecting to logout SAML and then {}", target);
+				return "redirect:" + target;
+			} else {
+				logger.trace("redirecting to {}", target);
+				return "redirect:" + redirectURL;
+			}
+		} else {
+			if (StringUtils.hasText(approved)) {
+				logger.trace("redirecting to logout SAML only");
+				return "redirect:" + getLogoutUrl(null);
+			} else {
+				return "logout_denied";
+			}
+		}
+	}
+
+	private boolean isUriValid(String redirectUri, ClientDetailsEntity client) {
+		return StringUtils.hasText(redirectUri)
+			&& client != null
+			&& client.getPostLogoutRedirectUris() != null
+			&& client.getPostLogoutRedirectUris().contains(redirectUri);
+	}
+
+	private String getLogoutUrl(String target) {
+		UriComponentsBuilder builder = UriComponentsBuilder.fromPath(SAMLLogoutFilter.FILTER_URL);
+		if (StringUtils.hasText(target)) {
+			builder.queryParam(PARAM_TARGET, target);
+		}
+		return builder.build().toString();
+	}
+
+	private String getRedirectUrl(String postLogoutRedirectUri, String state) {
+		UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(postLogoutRedirectUri);
+		if (StringUtils.hasText(state)) {
+			builder.queryParam(PARAM_STATE, state);
+		}
+		return builder.build().toString();
+	}
+
+}
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/JWKSetPublishingEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/JWKSetPublishingEndpoint.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/JWKSetPublishingEndpoint.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/JWKSetPublishingEndpoint.java
index 0c102a549..04d39c01e 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/JWKSetPublishingEndpoint.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/JWKSetPublishingEndpoint.java
@@ -15,12 +15,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.openid.connect.view.JWKSetView;
 import java.util.Map;
 
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.openid.connect.view.JWKSetView;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
 import org.springframework.stereotype.Controller;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ProtectedResourceRegistrationEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ProtectedResourceRegistrationEndpoint.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/ProtectedResourceRegistrationEndpoint.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ProtectedResourceRegistrationEndpoint.java
index 9e2e89b33..d5b8ed3c7 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ProtectedResourceRegistrationEndpoint.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ProtectedResourceRegistrationEndpoint.java
@@ -13,29 +13,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.view.ClientInformationResponseView;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
 import java.io.UnsupportedEncodingException;
 import java.text.ParseException;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.RegisteredClient;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.OAuth2TokenEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.exception.ValidationException;
-import org.mitre.openid.connect.service.OIDCTokenService;
-import org.mitre.openid.connect.view.ClientInformationResponseView;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonErrorView;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AuthMethod;
+import cz.muni.ics.oauth2.model.RegisteredClient;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.ClientDetailsEntityJsonProcessor;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.exception.ValidationException;
+import cz.muni.ics.openid.connect.service.OIDCTokenService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/RootController.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/RootController.java
similarity index 97%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/RootController.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/RootController.java
index f239a800d..0d5475c22 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/RootController.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/RootController.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Controller;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ServerConfigInterceptor.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ServerConfigInterceptor.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/ServerConfigInterceptor.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ServerConfigInterceptor.java
index f16693613..325049f9c 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ServerConfigInterceptor.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/ServerConfigInterceptor.java
@@ -18,13 +18,13 @@
 /**
  *
  */
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.config.UIConfiguration;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.config.UIConfiguration;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoEndpoint.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/UserInfoEndpoint.java
similarity index 91%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoEndpoint.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/UserInfoEndpoint.java
index 16950a092..e7874f380 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoEndpoint.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/UserInfoEndpoint.java
@@ -15,18 +15,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.UserInfoJWTView;
+import cz.muni.ics.openid.connect.view.UserInfoView;
 import java.util.List;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.UserInfoService;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.UserInfoJWTView;
-import org.mitre.openid.connect.view.UserInfoView;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.UserInfoService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/UserInfoInterceptor.java
similarity index 94%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/UserInfoInterceptor.java
index e58a1cda2..1f276200e 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/UserInfoInterceptor.java
@@ -18,11 +18,11 @@
 /**
  *
  */
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
-import org.mitre.openid.connect.model.OIDCAuthenticationToken;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.UserInfoService;
+import cz.muni.ics.openid.connect.model.OIDCAuthenticationToken;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.service.UserInfoService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/WhitelistAPI.java b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/WhitelistAPI.java
similarity index 95%
rename from openid-connect-server/src/main/java/org/mitre/openid/connect/web/WhitelistAPI.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/WhitelistAPI.java
index 8450c88ce..85980a96a 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/WhitelistAPI.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/openid/connect/web/WhitelistAPI.java
@@ -18,16 +18,16 @@
 /**
  *
  */
-package org.mitre.openid.connect.web;
+package cz.muni.ics.openid.connect.web;
 
+import cz.muni.ics.openid.connect.view.HttpCodeView;
+import cz.muni.ics.openid.connect.view.JsonEntityView;
+import cz.muni.ics.openid.connect.view.JsonErrorView;
 import java.security.Principal;
 import java.util.Collection;
 
-import org.mitre.openid.connect.model.WhitelistedSite;
-import org.mitre.openid.connect.service.WhitelistedSiteService;
-import org.mitre.openid.connect.view.HttpCodeView;
-import org.mitre.openid.connect.view.JsonEntityView;
-import org.mitre.openid.connect.view.JsonErrorView;
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.service.WhitelistedSiteService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/model/Claim.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Claim.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/uma/model/Claim.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Claim.java
index aaa88eb56..ed709ae09 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/model/Claim.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Claim.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.model;
+package cz.muni.ics.uma.model;
 
 import com.google.gson.JsonElement;
-import org.mitre.oauth2.model.convert.JsonElementStringConverter;
+import cz.muni.ics.oauth2.model.convert.JsonElementStringConverter;
 
 import java.util.Set;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/model/ClaimProcessingResult.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/ClaimProcessingResult.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/uma/model/ClaimProcessingResult.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/model/ClaimProcessingResult.java
index 8642b1b20..190034aaf 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/model/ClaimProcessingResult.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/ClaimProcessingResult.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.model;
+package cz.muni.ics.uma.model;
 
 import java.util.Collection;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/model/Permission.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Permission.java
similarity index 98%
rename from openid-connect-server/src/main/java/org/mitre/uma/model/Permission.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Permission.java
index a384cd9aa..4f6165209 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/model/Permission.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Permission.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.model;
+package cz.muni.ics.uma.model;
 
 import java.util.Set;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/model/PermissionTicket.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/PermissionTicket.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/uma/model/PermissionTicket.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/model/PermissionTicket.java
index 3bc7040c5..6a2bc48ce 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/model/PermissionTicket.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/PermissionTicket.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.model;
+package cz.muni.ics.uma.model;
 
 import java.util.Collection;
 import java.util.Date;
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/model/Policy.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Policy.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/uma/model/Policy.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Policy.java
index 064f9e0d8..286bd76eb 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/model/Policy.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/Policy.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.model;
+package cz.muni.ics.uma.model;
 
 import java.util.Collection;
 import java.util.Set;
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/model/ResourceSet.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/ResourceSet.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/uma/model/ResourceSet.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/model/ResourceSet.java
index ed73d1c73..bf769718c 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/model/ResourceSet.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/ResourceSet.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.uma.model;
+package cz.muni.ics.uma.model;
 
 import java.util.Collection;
 import java.util.HashSet;
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/model/SavedRegisteredClient.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/SavedRegisteredClient.java
similarity index 92%
rename from openid-connect-server/src/main/java/org/mitre/uma/model/SavedRegisteredClient.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/model/SavedRegisteredClient.java
index 34bf8c222..26c2c5d3f 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/model/SavedRegisteredClient.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/SavedRegisteredClient.java
@@ -14,8 +14,10 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.model;
+package cz.muni.ics.uma.model;
 
+import cz.muni.ics.oauth2.model.RegisteredClient;
+import cz.muni.ics.uma.model.convert.RegisteredClientStringConverter;
 import javax.persistence.Basic;
 import javax.persistence.Column;
 import javax.persistence.Convert;
@@ -25,9 +27,6 @@ import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Table;
 
-import org.mitre.oauth2.model.RegisteredClient;
-import org.mitre.uma.model.convert.RegisteredClientStringConverter;
-
 /**
  * @author jricher
  */
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/model/convert/RegisteredClientStringConverter.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/convert/RegisteredClientStringConverter.java
similarity index 90%
rename from openid-connect-server/src/main/java/org/mitre/uma/model/convert/RegisteredClientStringConverter.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/model/convert/RegisteredClientStringConverter.java
index 542cf75d2..54b15dbdd 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/model/convert/RegisteredClientStringConverter.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/model/convert/RegisteredClientStringConverter.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.model.convert;
+package cz.muni.ics.uma.model.convert;
 
+import cz.muni.ics.openid.connect.ClientDetailsEntityJsonProcessor;
 import javax.persistence.AttributeConverter;
 import javax.persistence.Converter;
 
-import org.mitre.oauth2.model.RegisteredClient;
-import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor;
+import cz.muni.ics.oauth2.model.RegisteredClient;
 import org.springframework.util.StringUtils;
 
 /**
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/repository/PermissionRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/repository/PermissionRepository.java
similarity index 87%
rename from openid-connect-server/src/main/java/org/mitre/uma/repository/PermissionRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/repository/PermissionRepository.java
index 2431e3fe7..b0e6baf93 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/repository/PermissionRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/repository/PermissionRepository.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.repository;
+package cz.muni.ics.uma.repository;
 
 import java.util.Collection;
 
-import org.mitre.uma.model.Permission;
-import org.mitre.uma.model.PermissionTicket;
-import org.mitre.uma.model.ResourceSet;
+import cz.muni.ics.uma.model.Permission;
+import cz.muni.ics.uma.model.PermissionTicket;
+import cz.muni.ics.uma.model.ResourceSet;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/repository/ResourceSetRepository.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/repository/ResourceSetRepository.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/uma/repository/ResourceSetRepository.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/repository/ResourceSetRepository.java
index f3aac1da2..74f3c4490 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/repository/ResourceSetRepository.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/repository/ResourceSetRepository.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.repository;
+package cz.muni.ics.uma.repository;
 
 import java.util.Collection;
 
-import org.mitre.uma.model.ResourceSet;
+import cz.muni.ics.uma.model.ResourceSet;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/service/ClaimsProcessingService.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/ClaimsProcessingService.java
similarity index 88%
rename from openid-connect-server/src/main/java/org/mitre/uma/service/ClaimsProcessingService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/service/ClaimsProcessingService.java
index a8a18a71b..40b092b88 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/service/ClaimsProcessingService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/ClaimsProcessingService.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.service;
+package cz.muni.ics.uma.service;
 
-import org.mitre.uma.model.ClaimProcessingResult;
-import org.mitre.uma.model.PermissionTicket;
-import org.mitre.uma.model.ResourceSet;
+import cz.muni.ics.uma.model.ClaimProcessingResult;
+import cz.muni.ics.uma.model.PermissionTicket;
+import cz.muni.ics.uma.model.ResourceSet;
 
 /**
  * Processes claims presented during an UMA transaction.
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/service/PermissionService.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/PermissionService.java
similarity index 93%
rename from openid-connect-server/src/main/java/org/mitre/uma/service/PermissionService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/service/PermissionService.java
index d79dc154a..38d52a9d4 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/service/PermissionService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/PermissionService.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.service;
+package cz.muni.ics.uma.service;
 
 import java.util.Set;
 
-import org.mitre.uma.model.PermissionTicket;
-import org.mitre.uma.model.ResourceSet;
+import cz.muni.ics.uma.model.PermissionTicket;
+import cz.muni.ics.uma.model.ResourceSet;
 import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
 
 
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/service/ResourceSetService.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/ResourceSetService.java
similarity index 91%
rename from openid-connect-server/src/main/java/org/mitre/uma/service/ResourceSetService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/service/ResourceSetService.java
index ef76ce1cb..e84448fde 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/service/ResourceSetService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/ResourceSetService.java
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.uma.service;
+package cz.muni.ics.uma.service;
 
 import java.util.Collection;
 
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.uma.model.ResourceSet;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.uma.model.ResourceSet;
 
 /**
  * Manage registered resource sets at this authorization server.
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/service/SavedRegisteredClientService.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/SavedRegisteredClientService.java
similarity index 89%
rename from openid-connect-server/src/main/java/org/mitre/uma/service/SavedRegisteredClientService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/service/SavedRegisteredClientService.java
index 4509db9a5..a616cae9f 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/service/SavedRegisteredClientService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/SavedRegisteredClientService.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.service;
+package cz.muni.ics.uma.service;
 
 import java.util.Collection;
 
-import org.mitre.oauth2.model.RegisteredClient;
-import org.mitre.uma.model.SavedRegisteredClient;
+import cz.muni.ics.oauth2.model.RegisteredClient;
+import cz.muni.ics.uma.model.SavedRegisteredClient;
 
 /**
  * @author jricher
diff --git a/openid-connect-server/src/main/java/org/mitre/uma/service/UmaTokenService.java b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/UmaTokenService.java
similarity index 86%
rename from openid-connect-server/src/main/java/org/mitre/uma/service/UmaTokenService.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/uma/service/UmaTokenService.java
index 1cea187a9..1113899b3 100644
--- a/openid-connect-server/src/main/java/org/mitre/uma/service/UmaTokenService.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/uma/service/UmaTokenService.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.uma.service;
+package cz.muni.ics.uma.service;
 
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.uma.model.PermissionTicket;
-import org.mitre.uma.model.Policy;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.uma.model.PermissionTicket;
+import cz.muni.ics.uma.model.Policy;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 
 /**
diff --git a/openid-connect-server/src/main/java/org/mitre/util/JsonUtils.java b/perun-oidc-server/src/main/java/cz/muni/ics/util/JsonUtils.java
similarity index 99%
rename from openid-connect-server/src/main/java/org/mitre/util/JsonUtils.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/util/JsonUtils.java
index be4e80e84..80c6cd594 100644
--- a/openid-connect-server/src/main/java/org/mitre/util/JsonUtils.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/util/JsonUtils.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.util;
+package cz.muni.ics.util;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -29,7 +29,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import org.mitre.oauth2.model.PKCEAlgorithm;
+import cz.muni.ics.oauth2.model.PKCEAlgorithm;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/openid-connect-server/src/main/java/org/mitre/util/jpa/JpaUtil.java b/perun-oidc-server/src/main/java/cz/muni/ics/util/jpa/JpaUtil.java
similarity index 96%
rename from openid-connect-server/src/main/java/org/mitre/util/jpa/JpaUtil.java
rename to perun-oidc-server/src/main/java/cz/muni/ics/util/jpa/JpaUtil.java
index 1a601ad86..bbf5deaf2 100644
--- a/openid-connect-server/src/main/java/org/mitre/util/jpa/JpaUtil.java
+++ b/perun-oidc-server/src/main/java/cz/muni/ics/util/jpa/JpaUtil.java
@@ -15,14 +15,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.util.jpa;
+package cz.muni.ics.util.jpa;
 
 import java.util.List;
 
 import javax.persistence.EntityManager;
 import javax.persistence.TypedQuery;
 
-import org.mitre.data.PageCriteria;
+import cz.muni.ics.data.PageCriteria;
 
 /**
  * @author mfranklin
diff --git a/openid-connect-server/src/test/java/org/mitre/data/AbstractPageOperationTemplateTest.java b/perun-oidc-server/src/test/java/cz/muni/ics/data/AbstractPageOperationTemplateTest.java
similarity index 99%
rename from openid-connect-server/src/test/java/org/mitre/data/AbstractPageOperationTemplateTest.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/data/AbstractPageOperationTemplateTest.java
index 1bf6d1e3f..062809ffc 100644
--- a/openid-connect-server/src/test/java/org/mitre/data/AbstractPageOperationTemplateTest.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/data/AbstractPageOperationTemplateTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.data;
+package cz.muni.ics.data;
 
 import java.util.ArrayList;
 import java.util.Collection;
diff --git a/openid-connect-server/src/test/java/org/mitre/discovery/util/TestWebfingerURLNormalizer.java b/perun-oidc-server/src/test/java/cz/muni/ics/discovery/util/TestWebfingerURLNormalizer.java
similarity index 99%
rename from openid-connect-server/src/test/java/org/mitre/discovery/util/TestWebfingerURLNormalizer.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/discovery/util/TestWebfingerURLNormalizer.java
index 34b494360..293f6487c 100644
--- a/openid-connect-server/src/test/java/org/mitre/discovery/util/TestWebfingerURLNormalizer.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/discovery/util/TestWebfingerURLNormalizer.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.discovery.util;
+package cz.muni.ics.discovery.util;
 
 import org.junit.Test;
 import org.springframework.web.util.UriComponents;
diff --git a/openid-connect-server/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java b/perun-oidc-server/src/test/java/cz/muni/ics/jose/TestJWKSetKeyStore.java
similarity index 98%
rename from openid-connect-server/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/jose/TestJWKSetKeyStore.java
index 188923443..baed83a2e 100644
--- a/openid-connect-server/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/jose/TestJWKSetKeyStore.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.jose;
+package cz.muni.ics.jose;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -24,7 +24,7 @@ import java.util.List;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mitre.jose.keystore.JWKSetKeyStore;
+import cz.muni.ics.jose.keystore.JWKSetKeyStore;
 import org.springframework.core.io.FileSystemResource;
 import org.springframework.core.io.Resource;
 
diff --git a/openid-connect-server/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java b/perun-oidc-server/src/test/java/cz/muni/ics/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java
similarity index 99%
rename from openid-connect-server/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java
index 18134b411..2b60604d7 100644
--- a/openid-connect-server/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.jwt.encryption.service.impl;
+package cz.muni.ics.jwt.encryption.service.impl;
 
 import java.security.NoSuchAlgorithmException;
 import java.security.spec.InvalidKeySpecException;
@@ -31,7 +31,7 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.mitre.jose.keystore.JWKSetKeyStore;
+import cz.muni.ics.jose.keystore.JWKSetKeyStore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/model/ClientDetailsEntityTest.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/model/ClientDetailsEntityTest.java
similarity index 96%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/model/ClientDetailsEntityTest.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/model/ClientDetailsEntityTest.java
index 3815b2d9a..cf6830d25 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/model/ClientDetailsEntityTest.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/model/ClientDetailsEntityTest.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import com.google.common.collect.ImmutableSet;
 import com.nimbusds.jose.EncryptionMethod;
@@ -36,7 +36,7 @@ import static org.junit.Assert.assertEquals;
 public class ClientDetailsEntityTest {
 
 	/**
-	 * Test method for {@link org.mitre.oauth2.model.ClientDetailsEntity#ClientDetailsEntity()}.
+	 * Test method for {@link ClientDetailsEntity#ClientDetailsEntity()}.
 	 */
 	@Test
 	public void testClientDetailsEntity() {
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/model/RegisteredClientTest.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/model/RegisteredClientTest.java
similarity index 95%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/model/RegisteredClientTest.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/model/RegisteredClientTest.java
index adb252cd7..8f09c3542 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/model/RegisteredClientTest.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/model/RegisteredClientTest.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.oauth2.model;
+package cz.muni.ics.oauth2.model;
 
 import com.google.common.collect.ImmutableSet;
 import com.nimbusds.jose.EncryptionMethod;
@@ -36,7 +36,7 @@ import static org.junit.Assert.assertEquals;
 public class RegisteredClientTest {
 
 	/**
-	 * Test method for {@link org.mitre.oauth2.model.RegisteredClient#RegisteredClient()}.
+	 * Test method for {@link RegisteredClient#RegisteredClient()}.
 	 */
 	@Test
 	public void testRegisteredClient() {
@@ -81,7 +81,7 @@ public class RegisteredClientTest {
 	}
 
 	/**
-	 * Test method for {@link org.mitre.oauth2.model.RegisteredClient#RegisteredClient(org.mitre.oauth2.model.ClientDetailsEntity)}.
+	 * Test method for {@link RegisteredClient#RegisteredClient(ClientDetailsEntity)}.
 	 */
 	@Test
 	public void testRegisteredClientClientDetailsEntity() {
@@ -128,7 +128,7 @@ public class RegisteredClientTest {
 	}
 
 	/**
-	 * Test method for {@link org.mitre.oauth2.model.RegisteredClient#RegisteredClient(org.mitre.oauth2.model.ClientDetailsEntity, java.lang.String, java.lang.String)}.
+	 * Test method for {@link RegisteredClient#RegisteredClient(ClientDetailsEntity, java.lang.String, java.lang.String)}.
 	 */
 	@Test
 	public void testRegisteredClientClientDetailsEntityStringString() {
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestDatabaseConfiguration.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/repository/impl/TestDatabaseConfiguration.java
similarity index 94%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestDatabaseConfiguration.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/repository/impl/TestDatabaseConfiguration.java
index 51ab0f43c..63b9d5b1a 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestDatabaseConfiguration.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/repository/impl/TestDatabaseConfiguration.java
@@ -1,4 +1,4 @@
-package org.mitre.oauth2.repository.impl;
+package cz.muni.ics.oauth2.repository.impl;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -45,7 +45,7 @@ public class TestDatabaseConfiguration {
 	@Bean(name = "defaultPersistenceUnit")
 	public FactoryBean<EntityManagerFactory> entityManagerFactory() {
 		LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
-		factory.setPackagesToScan("org.mitre", "org.mitre");
+		factory.setPackagesToScan("cz.muni.ics");
 		factory.setPersistenceProviderClass(org.eclipse.persistence.jpa.PersistenceProvider.class);
 		factory.setPersistenceUnitName("test" + System.currentTimeMillis());
 		factory.setDataSource(dataSource);
@@ -67,7 +67,7 @@ public class TestDatabaseConfiguration {
 			public Resource getResource(String location) {
 				String sql;
 				try {
-					sql = new String(Files.readAllBytes(Paths.get("..", "openid-connect-server-webapp", "src", "main",
+					sql = new String(Files.readAllBytes(Paths.get("..", "perun-oidc-server-webapp", "src", "main",
 							"resources", "db", "hsql", location)), UTF_8);
 				} catch (IOException e) {
 					throw new RuntimeException("Failed to read sql-script " + location, e);
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java
similarity index 91%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java
index 4ad4b355c..6ab3d5a01 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java
@@ -1,7 +1,11 @@
-package org.mitre.oauth2.repository.impl;
+package cz.muni.ics.oauth2.repository.impl;
 
 import static org.junit.Assert.assertEquals;
 
+import cz.muni.ics.oauth2.model.AuthenticationHolderEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.oauth2.model.SavedUserAuthentication;
 import java.util.Set;
 
 import javax.persistence.EntityManager;
@@ -10,10 +14,6 @@ import javax.persistence.PersistenceContext;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.AuthenticationHolderEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.model.SavedUserAuthentication;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringRunner;
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java
similarity index 93%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java
index 5507707f7..51cceef69 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java
@@ -14,21 +14,20 @@
  * limitations under the License.
  *******************************************************************************/
 
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
 import static org.mockito.Matchers.anyString;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.service.BlacklistedSiteService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.service.BlacklistedSiteService;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
 import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
-import org.springframework.security.oauth2.provider.ClientDetails;
 
 import com.google.common.collect.ImmutableSet;
 
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java
similarity index 97%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java
index 423cab6da..24d9e29b4 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
 import static com.google.common.collect.Sets.newHashSet;
 import static org.mockito.BDDMockito.given;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -27,11 +29,9 @@ import java.util.Set;
 import javax.swing.text.DateFormatter;
 
 import org.junit.Test;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.service.IntrospectionResultAssembler;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.uma.model.Permission;
+import cz.muni.ics.oauth2.service.IntrospectionResultAssembler;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.uma.model.Permission;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java
similarity index 96%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java
index adf060ae0..c98f3525b 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java
@@ -15,24 +15,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
 import com.google.common.collect.Sets;
+import cz.muni.ics.oauth2.repository.OAuth2TokenRepository;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.repository.OAuth2ClientRepository;
-import org.mitre.oauth2.repository.OAuth2TokenRepository;
-import org.mitre.oauth2.service.SystemScopeService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.model.WhitelistedSite;
-import org.mitre.openid.connect.service.ApprovedSiteService;
-import org.mitre.openid.connect.service.BlacklistedSiteService;
-import org.mitre.openid.connect.service.WhitelistedSiteService;
-import org.mitre.uma.model.ResourceSet;
-import org.mitre.uma.service.ResourceSetService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.repository.OAuth2ClientRepository;
+import cz.muni.ics.oauth2.service.SystemScopeService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.service.ApprovedSiteService;
+import cz.muni.ics.openid.connect.service.BlacklistedSiteService;
+import cz.muni.ics.openid.connect.service.WhitelistedSiteService;
+import cz.muni.ics.uma.model.ResourceSet;
+import cz.muni.ics.uma.service.ResourceSetService;
 import org.mockito.AdditionalAnswers;
 import org.mockito.ArgumentMatchers;
 import org.mockito.InjectMocks;
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java
similarity index 97%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java
index a409d15af..5136263a0 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java
@@ -15,8 +15,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
+import cz.muni.ics.oauth2.repository.OAuth2TokenRepository;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
@@ -24,15 +27,12 @@ import java.util.Set;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.AuthenticationHolderEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.repository.AuthenticationHolderRepository;
-import org.mitre.oauth2.repository.OAuth2TokenRepository;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.oauth2.service.SystemScopeService;
+import cz.muni.ics.oauth2.model.AuthenticationHolderEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.repository.AuthenticationHolderRepository;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.oauth2.service.SystemScopeService;
 import org.mockito.InjectMocks;
 import org.mockito.Matchers;
 import org.mockito.Mock;
diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultSystemScopeService.java
similarity index 97%
rename from openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultSystemScopeService.java
index 808d96b58..7775cc50a 100644
--- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/oauth2/service/impl/TestDefaultSystemScopeService.java
@@ -15,15 +15,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.oauth2.service.impl;
+package cz.muni.ics.oauth2.service.impl;
 
 import java.util.Set;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.SystemScope;
-import org.mitre.oauth2.repository.SystemScopeRepository;
+import cz.muni.ics.oauth2.model.SystemScope;
+import cz.muni.ics.oauth2.repository.SystemScopeRepository;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessorTest.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/ClientDetailsEntityJsonProcessorTest.java
similarity index 95%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessorTest.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/ClientDetailsEntityJsonProcessorTest.java
index 63a207907..fc9fd5d62 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessorTest.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/ClientDetailsEntityJsonProcessorTest.java
@@ -18,16 +18,16 @@
 /**
  *
  */
-package org.mitre.openid.connect;
+package cz.muni.ics.openid.connect;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.nimbusds.jose.EncryptionMethod;
 import com.nimbusds.jose.JWEAlgorithm;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.RegisteredClient;
 import org.junit.Test;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.RegisteredClient;
 
 import java.sql.Date;
 
@@ -41,7 +41,7 @@ import static org.junit.Assert.assertTrue;
 public class ClientDetailsEntityJsonProcessorTest {
 
 	/**
-	 * Test method for {@link org.mitre.openid.connect.ClientDetailsEntityJsonProcessor#parse(java.lang.String)}.
+	 * Test method for {@link ClientDetailsEntityJsonProcessor#parse(java.lang.String)}.
 	 */
 	@Test
 	public void testParse() {
@@ -86,7 +86,7 @@ public class ClientDetailsEntityJsonProcessorTest {
 	}
 
 	/**
-	 * Test method for {@link org.mitre.openid.connect.ClientDetailsEntityJsonProcessor#parseRegistered(java.lang.String)}.
+	 * Test method for {@link ClientDetailsEntityJsonProcessor#parseRegistered(java.lang.String)}.
 	 */
 	@Test
 	public void testParseRegistered() {
@@ -147,7 +147,7 @@ public class ClientDetailsEntityJsonProcessorTest {
 	}
 
 	/**
-	 * Test method for {@link org.mitre.openid.connect.ClientDetailsEntityJsonProcessor#serialize(org.mitre.oauth2.model.RegisteredClient)}.
+	 * Test method for {@link ClientDetailsEntityJsonProcessor#serialize(RegisteredClient)}.
 	 */
 	@Test
 	public void testSerialize() {
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
similarity index 97%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
index fde99f499..2d1ec7ab3 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java
@@ -1,4 +1,4 @@
-package org.mitre.openid.connect.assertion;
+package cz.muni.ics.openid.connect.assertion;
 
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.CoreMatchers.instanceOf;
@@ -8,6 +8,10 @@ import static org.junit.Assert.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.when;
 
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.jwt.signer.service.impl.ClientKeyCacheService;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
@@ -16,12 +20,8 @@ import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.jwt.signer.service.impl.ClientKeyCacheService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity.AuthMethod;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBeanTest.java
similarity index 99%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBeanTest.java
index 639b295d1..e30e652fb 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/ConfigurationPropertiesBeanTest.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import org.junit.Test;
 import org.springframework.beans.factory.BeanCreationException;
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/config/ServerConfigurationTest.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/ServerConfigurationTest.java
similarity index 95%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/config/ServerConfigurationTest.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/ServerConfigurationTest.java
index 7e7513e08..e10ab9ab2 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/config/ServerConfigurationTest.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/ServerConfigurationTest.java
@@ -18,7 +18,7 @@
 /**
  *
  */
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import org.junit.Test;
 
@@ -62,7 +62,7 @@ public class ServerConfigurationTest {
 
 
 	/**
-	 * Test method for {@link org.mitre.openid.connect.config.ServerConfiguration#equals(java.lang.Object)}.
+	 * Test method for {@link ServerConfiguration#equals(java.lang.Object)}.
 	 */
 	@Test
 	public void testEqualsObject() {
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/config/TestJsonMessageSource.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/TestJsonMessageSource.java
similarity index 96%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/config/TestJsonMessageSource.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/TestJsonMessageSource.java
index 04d7735d2..a62f94794 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/config/TestJsonMessageSource.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/config/TestJsonMessageSource.java
@@ -1,4 +1,4 @@
-package org.mitre.openid.connect.config;
+package cz.muni.ics.openid.connect.config;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultApprovedSiteService.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultApprovedSiteService.java
similarity index 89%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultApprovedSiteService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultApprovedSiteService.java
index daf257179..cd06517c8 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultApprovedSiteService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultApprovedSiteService.java
@@ -15,19 +15,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.repository.OAuth2TokenRepository;
+import cz.muni.ics.openid.connect.repository.ApprovedSiteRepository;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.repository.OAuth2TokenRepository;
-import org.mitre.openid.connect.model.ApprovedSite;
-import org.mitre.openid.connect.repository.ApprovedSiteRepository;
-import org.mitre.openid.connect.service.ApprovedSiteService;
+import cz.muni.ics.openid.connect.model.ApprovedSite;
+import cz.muni.ics.openid.connect.service.ApprovedSiteService;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java
similarity index 93%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java
index 79656cddd..ee4bf207a 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java
@@ -15,15 +15,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
+import cz.muni.ics.openid.connect.repository.BlacklistedSiteRepository;
 import java.util.Set;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.openid.connect.model.BlacklistedSite;
-import org.mitre.openid.connect.repository.BlacklistedSiteRepository;
+import cz.muni.ics.openid.connect.model.BlacklistedSite;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultOIDCTokenService.java
similarity index 89%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultOIDCTokenService.java
index 01405474c..f31fe4950 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultOIDCTokenService.java
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
 import java.util.Date;
 
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
 import org.springframework.security.oauth2.provider.OAuth2Request;
 
 import com.nimbusds.jose.JWSAlgorithm;
@@ -31,7 +31,6 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultUserInfoService.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultUserInfoService.java
similarity index 91%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultUserInfoService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultUserInfoService.java
index e5e707083..a0bb24a55 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultUserInfoService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultUserInfoService.java
@@ -18,18 +18,17 @@
 /**
  *
  */
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.model.DefaultUserInfo;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.repository.UserInfoRepository;
+import cz.muni.ics.openid.connect.service.PairwiseIdentiferService;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.model.DefaultUserInfo;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.repository.UserInfoRepository;
-import org.mitre.openid.connect.service.PairwiseIdentiferService;
 import org.mockito.InjectMocks;
 import org.mockito.Matchers;
 import org.mockito.Mock;
@@ -113,29 +112,29 @@ public class TestDefaultUserInfoService {
 
 		publicClient2 = new ClientDetailsEntity();
 		publicClient2.setClientId(publicClientId2);
-		publicClient2.setSubjectType(SubjectType.PUBLIC);
+		publicClient2.setSubjectType(ClientDetailsEntity.SubjectType.PUBLIC);
 
 		// pairwise set 1
 		pairwiseClient1 = new ClientDetailsEntity();
 		pairwiseClient1.setClientId(pairwiseClientId1);
-		pairwiseClient1.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient1.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 		pairwiseClient1.setSectorIdentifierUri(sectorIdentifier1);
 
 		pairwiseClient2 = new ClientDetailsEntity();
 		pairwiseClient2.setClientId(pairwiseClientId2);
-		pairwiseClient2.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient2.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 		pairwiseClient2.setSectorIdentifierUri(sectorIdentifier2);
 
 		// pairwise set 2
 		pairwiseClient3 = new ClientDetailsEntity();
 		pairwiseClient3.setClientId(pairwiseClientId3);
-		pairwiseClient3.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient3.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 		pairwiseClient3.setSectorIdentifierUri(sectorIdentifier3);
 
 		// pairwise with null sector
 		pairwiseClient4 = new ClientDetailsEntity();
 		pairwiseClient4.setClientId(pairwiseClientId4);
-		pairwiseClient4.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient4.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 
 
 
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java
similarity index 94%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java
index 40e8d6508..4df27d645 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java
@@ -15,13 +15,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
+import cz.muni.ics.openid.connect.model.WhitelistedSite;
+import cz.muni.ics.openid.connect.repository.WhitelistedSiteRepository;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.openid.connect.model.WhitelistedSite;
-import org.mitre.openid.connect.repository.WhitelistedSiteRepository;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.Mockito;
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java
similarity index 88%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java
index 30e9f5f51..fcd07c588 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java
@@ -18,20 +18,19 @@
 /**
  *
  */
-package org.mitre.openid.connect.service.impl;
+package cz.muni.ics.openid.connect.service.impl;
 
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.openid.connect.model.DefaultUserInfo;
+import cz.muni.ics.openid.connect.model.PairwiseIdentifier;
+import cz.muni.ics.openid.connect.model.UserInfo;
+import cz.muni.ics.openid.connect.repository.PairwiseIdentifierRepository;
 import java.util.Set;
 import java.util.UUID;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
-import org.mitre.openid.connect.model.DefaultUserInfo;
-import org.mitre.openid.connect.model.PairwiseIdentifier;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.repository.PairwiseIdentifierRepository;
 import org.mockito.InjectMocks;
 import org.mockito.Matchers;
 import org.mockito.Mock;
@@ -98,31 +97,31 @@ public class TestUUIDPairwiseIdentiferService {
 		// pairwise set 1
 		pairwiseClient1 = new ClientDetailsEntity();
 		pairwiseClient1.setClientId(pairwiseClientId1);
-		pairwiseClient1.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient1.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 		pairwiseClient1.setSectorIdentifierUri(sectorIdentifier1);
 
 		pairwiseClient2 = new ClientDetailsEntity();
 		pairwiseClient2.setClientId(pairwiseClientId2);
-		pairwiseClient2.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient2.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 		pairwiseClient2.setSectorIdentifierUri(sectorIdentifier2);
 
 		// pairwise set 2
 		pairwiseClient3 = new ClientDetailsEntity();
 		pairwiseClient3.setClientId(pairwiseClientId3);
-		pairwiseClient3.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient3.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 		pairwiseClient3.setSectorIdentifierUri(sectorIdentifier3);
 		pairwiseClient3.setRedirectUris(pairwiseClient3RedirectUris);
 
 		// pairwise with null sector
 		pairwiseClient4 = new ClientDetailsEntity();
 		pairwiseClient4.setClientId(pairwiseClientId4);
-		pairwiseClient4.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient4.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 		pairwiseClient4.setRedirectUris(pairwiseClient4RedirectUris);
 
 		// pairwise with multiple redirects and no sector (error)
 		pairwiseClient5 = new ClientDetailsEntity();
 		pairwiseClient5.setClientId(pairwiseClientId5);
-		pairwiseClient5.setSubjectType(SubjectType.PAIRWISE);
+		pairwiseClient5.setSubjectType(ClientDetailsEntity.SubjectType.PAIRWISE);
 		pairwiseClient5.setRedirectUris(pairwiseClient5RedirectUris);
 
 		// saved pairwise identifier from repository
@@ -134,7 +133,7 @@ public class TestUUIDPairwiseIdentiferService {
 	}
 
 	/**
-	 * Test method for {@link org.mitre.openid.connect.service.impl.UUIDPairwiseIdentiferService#getIdentifier(org.mitre.openid.connect.model.UserInfo, org.mitre.oauth2.model.ClientDetailsEntity)}.
+	 * Test method for {@link UUIDPairwiseIdentiferService#getIdentifier(UserInfo, ClientDetailsEntity)}.
 	 */
 	@Test
 	public void testGetIdentifier_existingEqual() {
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/token/TestConnectTokenEnhancer.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/token/TestConnectTokenEnhancer.java
similarity index 86%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/token/TestConnectTokenEnhancer.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/token/TestConnectTokenEnhancer.java
index 7e1acbfd9..5cb2ae777 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/token/TestConnectTokenEnhancer.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/token/TestConnectTokenEnhancer.java
@@ -15,18 +15,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.token;
+package cz.muni.ics.openid.connect.token;
 
+import cz.muni.ics.jwt.signer.service.JWTSigningAndValidationService;
+import cz.muni.ics.oauth2.model.ClientDetailsEntity;
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
+import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
+import cz.muni.ics.openid.connect.config.ConfigurationPropertiesBean;
+import cz.muni.ics.openid.connect.service.OIDCTokenService;
+import cz.muni.ics.openid.connect.service.UserInfoService;
 import java.text.ParseException;
 
-import org.mitre.jwt.signer.service.JWTSigningAndValidationService;
-import org.mitre.oauth2.model.ClientDetailsEntity;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
-import org.mitre.oauth2.service.ClientDetailsEntityService;
-import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
-import org.mitre.openid.connect.model.UserInfo;
-import org.mitre.openid.connect.service.OIDCTokenService;
-import org.mitre.openid.connect.service.UserInfoService;
 import org.springframework.security.oauth2.provider.OAuth2Authentication;
 import org.springframework.security.oauth2.provider.OAuth2Request;
 
@@ -40,7 +39,6 @@ import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
 
 import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet.Builder;
 
 @RunWith(MockitoJUnitRunner.class)
diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/util/TestIdTokenHashUtils.java b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/util/TestIdTokenHashUtils.java
similarity index 96%
rename from openid-connect-server/src/test/java/org/mitre/openid/connect/util/TestIdTokenHashUtils.java
rename to perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/util/TestIdTokenHashUtils.java
index b8ccbfd4c..a0f7758bd 100644
--- a/openid-connect-server/src/test/java/org/mitre/openid/connect/util/TestIdTokenHashUtils.java
+++ b/perun-oidc-server/src/test/java/cz/muni/ics/openid/connect/util/TestIdTokenHashUtils.java
@@ -15,15 +15,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *******************************************************************************/
-package org.mitre.openid.connect.util;
+package cz.muni.ics.openid.connect.util;
 
 
+import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
 import java.text.ParseException;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.runners.MockitoJUnitRunner;
@@ -43,7 +43,7 @@ import static org.junit.Assert.assertEquals;
 public class TestIdTokenHashUtils {
 
 	@Mock
-	OAuth2AccessTokenEntity mockToken256;
+    OAuth2AccessTokenEntity mockToken256;
 	@Mock
 	OAuth2AccessTokenEntity mockToken384;
 	@Mock
diff --git a/openid-connect-server/src/test/resources/resources/js/locale/en/messages.json b/perun-oidc-server/src/test/resources/resources/js/locale/en/messages.json
similarity index 100%
rename from openid-connect-server/src/test/resources/resources/js/locale/en/messages.json
rename to perun-oidc-server/src/test/resources/resources/js/locale/en/messages.json
diff --git a/pom.xml b/pom.xml
index aa7792387..8114922b6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,111 +18,55 @@
  -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<modelVersion>4.0.0</modelVersion>
-	<groupId>org.mitre</groupId>
-	<artifactId>openid-connect-parent</artifactId>
+	<groupId>cz.muni.ics</groupId>
+	<artifactId>perun-oidc-parent</artifactId>
 	<version>2.0.0</version>
-	<name>MITREid Connect</name>
 	<packaging>pom</packaging>
+
+	<modules>
+		<module>perun-oidc-server</module>
+		<module>perun-oidc-server-webapp</module>
+	</modules>
+
+	<name>CESNET OpenID-Connect-Java-Spring-Server</name>
+	<description>OpenID Connect Provider server based on Java and Spring. It comes out of the MITREid Connect implementation.</description>
+	<url>https://github.com/CESNET/OpenID-Connect-Java-Spring-Server.git</url>
+
 	<licenses>
 		<license>
 			<name>The Apache Software License, Version 2.0</name>
-			<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+			<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
 			<distribution>repo</distribution>
 			<comments>A business-friendly OSS license</comments>
 		</license>
 	</licenses>
-	<modules>
-		<module>openid-connect-server</module>
-		<module>openid-connect-server-webapp</module>
-	</modules>
-
-	<scm>
-		<connection>scm:git:https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server.git</connection>
-		<developerConnection>scm:git:git@github.com:mitreid-connect/OpenID-Connect-Java-Spring-Server.git</developerConnection>
-		<url>https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server.git</url>
-		<tag>HEAD</tag>
-	</scm>
 
 	<developers>
 		<developer>
-			<id>jricher</id>
-			<name>Justin Richer</name>
-			<email>jricher@mit.edu</email>
+			<name>Dominik František Bučík</name>
+			<email>bucik@ics.muni.cz</email>
+			<organization>ICS MUNI</organization>
+			<organizationUrl>https://ics.muni.cz/</organizationUrl>
 		</developer>
 	</developers>
 
-	<mailingLists>
-		<mailingList>
-			<archive>https://mailman.mit.edu/mailman/listinfo/mitreid-connect</archive>
-			<name>MITREid Connect Mailing List</name>
-			<post>mitreid-connect@mit.edu</post>
-		</mailingList>
-	</mailingLists>
+	<scm>
+		<connection>scm:git:git://github.com/CESNET/OpenID-Connect-Java-Spring-Server.git</connection>
+		<developerConnection>scm:git:ssh://github.com:CESNET/OpenID-Connect-Java-Spring-Server.git</developerConnection>
+		<url>https://github.com/CESNET/OpenID-Connect-Java-Spring-Server.git/tree/master</url>
+	</scm>
 
-	<description>A reference implementation of OpenID Connect (http://openid.net/connect/), OAuth 2.0, and UMA built on top of Java, Spring, and Spring Security. The project contains a fully functioning server, client, and utility library.</description>
-	<url>https://github.com/mitreid-connect</url>
-	<build>
-		<pluginManagement>
-			<plugins>
-				<plugin>
-					<groupId>org.apache.maven.plugins</groupId>
-					<artifactId>maven-jar-plugin</artifactId>
-					<version>3.2.0</version>
-				</plugin>
-				<plugin>
-					<groupId>org.apache.maven.plugins</groupId>
-					<artifactId>maven-war-plugin</artifactId>
-					<version>3.3.1</version>
-				</plugin>
-				<plugin>
-					<groupId>org.apache.maven.plugins</groupId>
-					<artifactId>maven-source-plugin</artifactId>
-					<version>3.2.1</version>
-				</plugin>
-				<plugin>
-					<groupId>org.apache.maven.plugins</groupId>
-					<artifactId>maven-compiler-plugin</artifactId>
-					<version>3.8.1</version>
-				</plugin>
-				<plugin>
-					<groupId>org.appfuse.plugins</groupId>
-					<artifactId>warpath-maven-plugin</artifactId>
-					<version>3.5.0</version>
-				</plugin>
-			</plugins>
-		</pluginManagement>
-		<plugins>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-source-plugin</artifactId>
-				<executions>
-					<execution>
-						<id>attach-sources</id>
-						<goals>
-							<goal>jar</goal>
-						</goals>
-					</execution>
-				</executions>
-			</plugin>
-			<plugin>
-				<groupId>org.apache.maven.plugins</groupId>
-				<artifactId>maven-compiler-plugin</artifactId>
-				<configuration>
-					<source>${java-version}</source>
-					<target>${java-version}</target>
-				</configuration>
-			</plugin>
-		</plugins>
-	</build>
-
-	<issueManagement>
-		<url>https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues</url>
-		<system>GitHub Issues</system>
-	</issueManagement>
-	<ciManagement>
-		<system>Travis CI</system>
-		<url>https://travis-ci.org/mitreid-connect/OpenID-Connect-Java-Spring-Server</url>
-	</ciManagement>
+	<repositories>
+		<repository>
+			<id>jitpack.io</id>
+			<url>https://jitpack.io</url>
+		</repository>
+		<repository>
+			<id>Shibboleth</id>
+			<name>Shibboleth</name>
+			<url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
+		</repository>
+	</repositories>
 
 	<properties>
 		<java-version>8</java-version>
@@ -134,15 +78,12 @@
 		<spring-security.version>4.2.20.RELEASE</spring-security.version>
 		<spring-security-oauth2.version>2.5.1.RELEASE</spring-security-oauth2.version>
 		<jackson.version>2.11.4</jackson.version>
-		<jstl.version>1.2</jstl.version>
-		<servlet-api.version>2.5</servlet-api.version>
 		<jsp-api.version>2.2</jsp-api.version>
 		<mysql.version>8.0.23</mysql.version>
 		<hsqldb.version>2.4.0</hsqldb.version>
 		<eclipse-persistence.version>2.7.7</eclipse-persistence.version>
 		<javax-persistence.version>2.2.1</javax-persistence.version>
 		<hikari.version>3.4.5</hikari.version>
-		<logback.version>1.2.3</logback.version>
 		<org.slf4j-version>1.7.30</org.slf4j-version>
 		<log4j-core.version>2.13.3</log4j-core.version>
 		<junit.version>4.13.1</junit.version>
@@ -156,14 +97,42 @@
 		<commons-io.version>2.7</commons-io.version>
 		<javax-annotation-api.version>1.3.2</javax-annotation-api.version>
 		<lombok.version>1.18.18</lombok.version>
+		<servlet-api.version>2.5</servlet-api.version>
+		<jstl.version>1.2</jstl.version>
+		<apache-commons.version>3.9</apache-commons.version>
+		<jackson-dataformat-yml.verson>2.11.4</jackson-dataformat-yml.verson>
+		<mariadb-java-client.version>2.7.0</mariadb-java-client.version>
+		<apache-directory-api.version>2.0.1</apache-directory-api.version>
+		<janino.version>3.0.12</janino.version>
+		<logback.verison>1.2.3</logback.verison>
+		<jul-to-slf4j.version>1.7.30</jul-to-slf4j.version>
+		<shedlock.version>4.3.1</shedlock.version>
+		<javax-annotation-api.version>1.3.2</javax-annotation-api.version>
+		<jakarta-xml-bind-api.version>2.3.3</jakarta-xml-bind-api.version>
+		<jaxb-runtime.version>2.3.3</jaxb-runtime.version>
+		<jakarta-servlet-api.version>4.0.4</jakarta-servlet-api.version>
+		<aspectjweaver.version>1.9.6</aspectjweaver.version>
+		<spring-aop.version>4.3.30.RELEASE</spring-aop.version>
+		<spring-context.version>4.3.30.RELEASE</spring-context.version>
+		<spring-security-saml2.version>1.0.10.RELEASE</spring-security-saml2.version>
+		<!-- BUILD -->
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<java-version>8</java-version>
+		<maven.compiler.source>${java-version}</maven.compiler.source>
+		<maven.compiler.target>${java-version}</maven.compiler.target>
 	</properties>
 
 	<dependencyManagement>
 		<dependencies>
 			<dependency>
-				<groupId>javax.annotation</groupId>
-				<artifactId>javax.annotation-api</artifactId>
-				<version>${javax-annotation-api.version}</version>
+				<groupId>cz.muni.ics</groupId>
+				<artifactId>perun-oidc-server</artifactId>
+				<version>${project.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>cz.muni.ics</groupId>
+				<artifactId>perun-oidc-server-webapp</artifactId>
+				<version>${project.version}</version>
 			</dependency>
 			<!-- Spring -->
 			<dependency>
@@ -203,12 +172,6 @@
 				<artifactId>jstl-api</artifactId>
 				<version>${jstl.version}</version>
 			</dependency>
-			<dependency>
-				<groupId>javax.servlet</groupId>
-				<artifactId>servlet-api</artifactId>
-				<version>${servlet-api.version}</version>
-				<scope>compile</scope>
-			</dependency>
 			<dependency>
 				<groupId>javax.servlet.jsp</groupId>
 				<artifactId>jsp-api</artifactId>
@@ -237,12 +200,6 @@
 				<artifactId>HikariCP</artifactId>
 				<version>${hikari.version}</version>
 			</dependency>
-			<!-- Logging -->
-			<dependency>
-				<groupId>ch.qos.logback</groupId>
-				<artifactId>logback-classic</artifactId>
-				<version>${logback.version}</version>
-			</dependency>
 			<!-- Test -->
 			<dependency>
 				<groupId>junit</groupId>
@@ -297,7 +254,165 @@
 				<artifactId>lombok</artifactId>
 				<version>${lombok.version}</version>
 			</dependency>
+			<dependency>
+				<groupId>javax.servlet</groupId>
+				<artifactId>servlet-api</artifactId>
+				<version>${servlet-api.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>javax.servlet</groupId>
+				<artifactId>jstl</artifactId>
+				<version>${jstl.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.apache.commons</groupId>
+				<artifactId>commons-lang3</artifactId>
+				<version>${apache-commons.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>com.fasterxml.jackson.dataformat</groupId>
+				<artifactId>jackson-dataformat-yaml</artifactId>
+				<version>${jackson-dataformat-yml.verson}</version>
+			</dependency>
+			<!-- MySQL JDBC driver -->
+			<dependency>
+				<groupId>org.mariadb.jdbc</groupId>
+				<artifactId>mariadb-java-client</artifactId>
+				<version>${mariadb-java-client.version}</version>
+			</dependency>
+			<!-- LDAP -->
+			<dependency>
+				<groupId>org.apache.directory.api</groupId>
+				<artifactId>api-all</artifactId>
+				<version>${apache-directory-api.version}</version>
+			</dependency>
+			<!-- logging -->
+			<dependency>
+				<groupId>org.codehaus.janino</groupId>
+				<artifactId>janino</artifactId>
+				<version>${janino.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>ch.qos.logback</groupId>
+				<artifactId>logback-classic</artifactId>
+				<version>${logback.verison}</version>
+				<exclusions>
+					<exclusion>
+						<groupId>org.slf4j</groupId>
+						<artifactId>slf4j-api</artifactId>
+					</exclusion>
+				</exclusions>
+			</dependency>
+			<dependency>
+				<groupId>org.slf4j</groupId>
+				<artifactId>jul-to-slf4j</artifactId>
+				<version>${jul-to-slf4j.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>net.javacrumbs.shedlock</groupId>
+				<artifactId>shedlock-spring</artifactId>
+				<version>${shedlock.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>net.javacrumbs.shedlock</groupId>
+				<artifactId>shedlock-provider-jdbc-template</artifactId>
+				<version>${shedlock.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>javax.annotation</groupId>
+				<artifactId>javax.annotation-api</artifactId>
+				<version>${javax-annotation-api.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>jakarta.xml.bind</groupId>
+				<artifactId>jakarta.xml.bind-api</artifactId>
+				<version>${jakarta-xml-bind-api.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.glassfish.jaxb</groupId>
+				<artifactId>jaxb-runtime</artifactId>
+				<version>${jaxb-runtime.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>jakarta.servlet</groupId>
+				<artifactId>jakarta.servlet-api</artifactId>
+				<version>${jakarta-servlet-api.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-aop</artifactId>
+				<version>${spring-aop.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.aspectj</groupId>
+				<artifactId>aspectjweaver</artifactId>
+				<version>${aspectjweaver.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework</groupId>
+				<artifactId>spring-context</artifactId>
+				<version>${spring-context.version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.springframework.security.extensions</groupId>
+				<artifactId>spring-security-saml2-core</artifactId>
+				<version>${spring-security-saml2.version}</version>
+			</dependency>
 		</dependencies>
 	</dependencyManagement>
 
+	<build>
+		<pluginManagement>
+			<plugins>
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-jar-plugin</artifactId>
+					<version>3.2.0</version>
+				</plugin>
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-war-plugin</artifactId>
+					<version>3.3.1</version>
+				</plugin>
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-source-plugin</artifactId>
+					<version>3.2.1</version>
+				</plugin>
+				<plugin>
+					<groupId>org.apache.maven.plugins</groupId>
+					<artifactId>maven-compiler-plugin</artifactId>
+					<version>3.8.1</version>
+				</plugin>
+				<plugin>
+					<groupId>org.appfuse.plugins</groupId>
+					<artifactId>warpath-maven-plugin</artifactId>
+					<version>3.5.0</version>
+				</plugin>
+			</plugins>
+		</pluginManagement>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>${java-version}</source>
+					<target>${java-version}</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
 </project>