├── docs ├── pagebreak.tex ├── images │ ├── OpenID.png │ ├── AdaSecurity.jpg │ ├── PolicyModel.png │ └── ModelOverview.png ├── Security_Auth_OAuth_Googleplus.md ├── requirements.txt ├── title.md ├── Security_Controllers.md ├── Security_Random.md ├── Security_OAuth_JWT.md ├── index.md ├── links.txt ├── Installation.md ├── Security_OAuth_Servers.md ├── Security_Auth.md ├── pandoc.css ├── Security.md ├── Security_OAuth.md └── Security_Policies.md ├── uml └── security.zargo ├── regtests ├── files │ ├── permissions │ │ ├── empty.xml │ │ ├── simple-policy.xml │ │ ├── policy-with-role.xml │ │ └── grants.xml │ ├── discover │ │ ├── steamcommunity.xrds │ │ ├── livejournal.xrds │ │ ├── myspace.xrds │ │ ├── orange.xrds │ │ ├── google.xrds │ │ ├── claimid.xrds │ │ ├── yahoo.xrds │ │ ├── verisign.xrds │ │ └── myopenid.xrds │ └── user_apps.properties ├── src │ ├── security-testsuite.ads │ ├── security_harness.adb │ ├── security-random-tests.ads │ ├── security-oauth-clients-tests.ads │ ├── security-oauth-jwt-tests.ads │ ├── security-auth-tests.ads │ ├── security-testsuite.adb │ ├── security-oauth-servers-tests.ads │ ├── security-permissions-tests.ads │ ├── security-policies-tests.ads │ ├── security-random-tests.adb │ ├── security-permissions-tests.adb │ ├── security-oauth-jwt-tests.adb │ ├── security-oauth-clients-tests.adb │ └── security-auth-tests.adb ├── security_tests.gpr └── alire.toml ├── samples ├── web │ ├── atlas │ │ ├── images │ │ │ ├── icons-1.png │ │ │ └── open-id-logos-1.png │ │ ├── login.html │ │ └── css │ │ │ ├── grids │ │ │ ├── reset.css │ │ │ ├── reset_rtl.css │ │ │ └── fluid_grid.css │ │ │ ├── users.css │ │ │ └── awa.css │ └── success.thtml ├── samples.gpr ├── alire.toml └── src │ ├── auth_cb.ads │ └── auth_demo.adb ├── .readthedocs.yaml ├── mkdocs.yml ├── dynamo.xml ├── coverage.sh ├── NOTICE.txt ├── security.gpr ├── src ├── security-auth-oauth-yahoo.ads ├── security-auth-oauth-github.ads ├── security-controllers-urls.adb ├── security-controllers-urls.ads ├── security-auth-oauth-googleplus.ads ├── security-controllers-roles.ads ├── security-auth-oauth-facebook.ads ├── security-controllers-roles.adb ├── security-controllers.ads ├── security-auth-oauth-github.adb ├── security-oauth.adb ├── security-random.ads ├── security-auth-oauth.ads ├── security-permissions.ads ├── security-auth-oauth-googleplus.adb ├── security-oauth-jwt.ads ├── security-auth-oauth-yahoo.adb ├── security-auth-openid.ads ├── security.ads ├── security-oauth.ads ├── security-auth-oauth.adb ├── security-random.adb └── security-oauth-jwt.adb ├── Makefile ├── NEWS.md ├── security_config.gpr ├── alire.toml ├── samples.properties └── README.md /docs/pagebreak.tex: -------------------------------------------------------------------------------- 1 | \newpage 2 | -------------------------------------------------------------------------------- /uml/security.zargo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stcarrez/ada-security/HEAD/uml/security.zargo -------------------------------------------------------------------------------- /docs/images/OpenID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stcarrez/ada-security/HEAD/docs/images/OpenID.png -------------------------------------------------------------------------------- /regtests/files/permissions/empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/images/AdaSecurity.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stcarrez/ada-security/HEAD/docs/images/AdaSecurity.jpg -------------------------------------------------------------------------------- /docs/images/PolicyModel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stcarrez/ada-security/HEAD/docs/images/PolicyModel.png -------------------------------------------------------------------------------- /docs/images/ModelOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stcarrez/ada-security/HEAD/docs/images/ModelOverview.png -------------------------------------------------------------------------------- /samples/web/atlas/images/icons-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stcarrez/ada-security/HEAD/samples/web/atlas/images/icons-1.png -------------------------------------------------------------------------------- /samples/web/atlas/images/open-id-logos-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stcarrez/ada-security/HEAD/samples/web/atlas/images/open-id-logos-1.png -------------------------------------------------------------------------------- /docs/Security_Auth_OAuth_Googleplus.md: -------------------------------------------------------------------------------- 1 | ### Google+ 2 | The Google+ authentication is based on OAuth 2.0 and the OpenID Connect Basic Client Profile. 3 | 4 | See https://developers.google.com/accounts/docs/OAuth2Login 5 | 6 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | mkdocs: 9 | configuration: mkdocs.yml 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt 14 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs==1.6.1 2 | mkdocs-material==9.5.34 3 | Pygments==2.18.0 4 | pymdown-extensions==10.9 5 | markdown==3.7 6 | mkdocs-markdownextradata-plugin==0.2.6 7 | markdown-include==0.8.1 8 | mkdocs-redirects==1.2.1 9 | jinja2==3.1.4 10 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Ada Security Library 2 | docs_dir: docs 3 | nav: 4 | - Introduction: index.md 5 | - Installation: Installation.md 6 | - Overview: Security.md 7 | - Authentication: Security_Auth.md 8 | - OAuth2: Security_OAuth.md 9 | - Policies: Security_Policies.md 10 | theme: readthedocs 11 | -------------------------------------------------------------------------------- /dynamo.xml: -------------------------------------------------------------------------------- 1 | 2 | security 3 | docs/links.txt 4 | Stephane.Carrez@gmail.com 5 | .;../ada-util 6 | Stephane Carrez 7 | 8 | -------------------------------------------------------------------------------- /regtests/files/discover/steamcommunity.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http://specs.openid.net/auth/2.0/server 6 | https://steamcommunity.com/openid/login 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/title.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Ada Security Library Programmer's Guide" 3 | author: [Stephane Carrez] 4 | date: 2024-10-12 5 | subject: "Ada Security Library" 6 | tags: [Ada, Security, Permission, OAuth2, Policies, JWT] 7 | titlepage: true 8 | titlepage-color: 06386e 9 | titlepage-text-color: FFFFFF 10 | titlepage-rule-color: FFFFFF 11 | titlepage-rule-height: 1 12 | ... 13 | -------------------------------------------------------------------------------- /regtests/files/discover/livejournal.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://specs.openid.net/auth/2.0/signon 5 | http://www.livejournal.com/openid/server.bml 6 | http://joe.livejournal.com/ 7 | 8 | 9 | -------------------------------------------------------------------------------- /coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | NAME=security.cov 3 | alr exec -- lcov --quiet --base-directory . --directory . \ 4 | --no-external --ignore-errors gcov,unused \ 5 | --exclude '*/' \ 6 | --exclude '*/b__*.adb' \ 7 | --exclude '*/samples/*' \ 8 | --exclude '*/regtests*' -c -o $NAME 9 | rm -rf cover 10 | genhtml --quiet --ignore-errors source -o ./cover -t "test coverage" --num-spaces 4 $NAME 11 | 12 | -------------------------------------------------------------------------------- /regtests/files/user_apps.properties: -------------------------------------------------------------------------------- 1 | apps.list=1,2,tst3 2 | apps.1.client_id=app-id-1 3 | apps.1.client_secret=app-secret-1 4 | apps.2.client_id=app-id-2 5 | apps.2.client_secret=app-secret-2 6 | apps.tst3.client_id=app-id-tst3 7 | apps.tst3.client_secret=app-secret-tst3 8 | 9 | users.list=joe,admin 10 | users.joe.username=joe 11 | users.joe.password=test 12 | users.admin.username=admin 13 | users.admin.password=admin 14 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Ada Security Library 2 | Copyright 2011-2021 Stephane Carrez 3 | 4 | This product includes software developed by: 5 | Stephane Carrez (https://blog.vacs.fr/). 6 | 7 | ------------------------------------------------------------------------ 8 | See the file LICENSE.txt 9 | See licenses for accompanying products in the "/licenses" subdirectory. 10 | ------------------------------------------------------------------------ 11 | 12 | -------------------------------------------------------------------------------- /regtests/files/discover/myspace.xrds: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://specs.openid.net/auth/2.0/signon 9 | http://specs.openid.net/extensions/oauth/1.0 10 | http://api.myspace.com/openid 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/Security_Controllers.md: -------------------------------------------------------------------------------- 1 | ## Security Controller 2 | The Security.Controllers package defines the security controller used to 3 | verify that a given permission is granted. A security controller uses the security 4 | context and other controller specific and internal data to verify that the permission 5 | is granted. 6 | 7 | Security controller instances are created when the security policy rules are parsed. 8 | These instances are shared across possibly several concurrent requests. 9 | 10 | -------------------------------------------------------------------------------- /regtests/files/discover/orange.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http://specs.openid.net/auth/2.0/server 6 | http://openid.net/srv/ax/1.0 7 | http://openid.net/sreg/1.0 8 | http://openid.net/extensions/sreg/1.1 9 | http://openid.orange.fr/server/ 10 | 11 | 12 | -------------------------------------------------------------------------------- /regtests/src/security-testsuite.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security testsuite - Ada Security Test suite 3 | -- Copyright (C) 2011, 2012 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Tests; 9 | 10 | package Security.Testsuite is 11 | 12 | function Suite return Util.Tests.Access_Test_Suite; 13 | 14 | end Security.Testsuite; 15 | -------------------------------------------------------------------------------- /docs/Security_Random.md: -------------------------------------------------------------------------------- 1 | ## Random Generator 2 | The Security.Random package defines the Generator tagged type 3 | which provides operations to generate random tokens intended to be used for 4 | a nonce, access token, salt or other purposes. The generator is intended to be 5 | used in multi-task environments as it implements the low level random generation 6 | within a protected type. The generator defines a Generate operation 7 | that returns either a binary random array or the base64url encoding of the 8 | binary array. 9 | 10 | -------------------------------------------------------------------------------- /regtests/files/discover/google.xrds: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | http://specs.openid.net/auth/2.0/server 6 | http://openid.net/srv/ax/1.0 7 | http://specs.openid.net/extensions/ui/1.0/mode/popup 8 | http://specs.openid.net/extensions/ui/1.0/icon 9 | http://specs.openid.net/extensions/pape/1.0 10 | https://www.google.com/accounts/o8/ud 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /regtests/src/security_harness.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security -- Unit tests for the Ada Security 3 | -- Copyright (C) 2012 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Tests; 9 | with Security.Testsuite; 10 | procedure Security_Harness is 11 | 12 | procedure Harness is new Util.Tests.Harness (Security.Testsuite.Suite); 13 | 14 | begin 15 | Harness ("security-tests.xml"); 16 | end Security_Harness; 17 | -------------------------------------------------------------------------------- /samples/samples.gpr: -------------------------------------------------------------------------------- 1 | with "security_config"; 2 | with "utilada_sys"; 3 | with "utilada_aws"; 4 | with "security"; 5 | with "aws"; 6 | 7 | project Samples is 8 | 9 | Mains := ("auth_demo.adb"); 10 | 11 | for Main use Mains; 12 | 13 | for Source_Dirs use ("src"); 14 | 15 | for Object_Dir use "../" & Security_Config'Object_Dir & "/obj"; 16 | for Exec_Dir use "../bin"; 17 | 18 | package Binder renames Security_Config.Binder; 19 | package Builder renames Security_Config.Builder; 20 | package Compiler renames Security_Config.Compiler; 21 | package Linker renames Security_Config.Linker; 22 | 23 | end Samples; 24 | -------------------------------------------------------------------------------- /regtests/security_tests.gpr: -------------------------------------------------------------------------------- 1 | with "security_config"; 2 | with "utilada_unit"; 3 | with "security"; 4 | 5 | project Security_Tests is 6 | 7 | for Languages use ("Ada"); 8 | 9 | Mains := ("security_harness.adb"); 10 | 11 | for Source_Dirs use ("src"); 12 | 13 | for Main use Mains; 14 | 15 | for Object_Dir use "../" & Security_Config'Object_Dir & "/obj/regtests"; 16 | for Exec_Dir use "../bin"; 17 | 18 | package Binder renames Security_Config.Binder; 19 | package Builder renames Security_Config.Builder; 20 | package Compiler renames Security_Config.Compiler; 21 | package Linker renames Security_Config.Linker; 22 | 23 | end Security_Tests; 24 | -------------------------------------------------------------------------------- /regtests/alire.toml: -------------------------------------------------------------------------------- 1 | description = "Uni tests for Ada Security Library" 2 | name = "security_tests" 3 | version = "1.5.1" 4 | licenses = "Apache-2.0" 5 | maintainers = ["Stephane.Carrez@gmail.com"] 6 | maintainers-logins = ["stcarrez"] 7 | authors = ["Stephane.Carrez@gmail.com"] 8 | project-files = ["security_tests.gpr"] 9 | tags = ["security", "oauth2", "authentication", "permissions", "jwt"] 10 | website = "https://gitlab.com/stcarrez/ada-security" 11 | long-description = """ 12 | 13 | Unit tests for Ada Security 14 | 15 | """ 16 | 17 | [[depends-on]] 18 | security = "*" 19 | utilada = "*" 20 | utilada_unit = "*" 21 | 22 | [configuration] 23 | disabled = true 24 | 25 | [[pins]] 26 | security = { path = '..' } 27 | -------------------------------------------------------------------------------- /regtests/files/discover/claimid.xrds: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://openid.net/signon/1.0 9 | https://openid.claimid.com/server 10 | https://openid.claimid.com/joe 11 | 12 | 13 | http://openid.net/signon/1.0 14 | https://openid.claimid.com/server 15 | https://openid.claimid.com/joe 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/alire.toml: -------------------------------------------------------------------------------- 1 | description = "Examples for Ada Security Library" 2 | name = "security_samples" 3 | version = "1.5.2" 4 | licenses = "Apache-2.0" 5 | maintainers = ["Stephane.Carrez@gmail.com"] 6 | maintainers-logins = ["stcarrez"] 7 | authors = ["Stephane.Carrez@gmail.com"] 8 | project-files = ["samples.gpr"] 9 | tags = ["security", "oauth2", "authentication", "permissions", "jwt"] 10 | website = "https://gitlab.com/stcarrez/ada-security" 11 | long-description = """ 12 | 13 | Examples for Ada Security 14 | 15 | """ 16 | 17 | [[depends-on]] 18 | security = "*" 19 | utilada = "*" 20 | utilada_aws = "*" 21 | aws = "^24.0" 22 | 23 | [configuration] 24 | disabled = true 25 | 26 | [[pins]] 27 | security = { path = '..' } 28 | -------------------------------------------------------------------------------- /regtests/src/security-random-tests.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-random-tests - Tests for random package 3 | -- Copyright (C) 2017, 2022 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Tests; 9 | package Security.Random.Tests is 10 | 11 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite); 12 | 13 | type Test is new Util.Tests.Test with null record; 14 | 15 | procedure Test_Generate (T : in out Test); 16 | 17 | procedure Test_Generate_String (T : in out Test); 18 | 19 | end Security.Random.Tests; 20 | -------------------------------------------------------------------------------- /docs/Security_OAuth_JWT.md: -------------------------------------------------------------------------------- 1 | ### JSON Web Token 2 | JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred 3 | between two parties. A JWT token is returned by an authorization server. It contains 4 | useful information that allows to verify the authentication and identify the user. 5 | 6 | The Security.OAuth.JWT package implements the decoding part of JWT defined in: 7 | JSON Web Token (JWT), http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-07 8 | 9 | A list of pre-defined ID tokens are returned in the JWT token claims and used for 10 | the OpenID Connect. This is specified in 11 | OpenID Connect Basic Client Profile 1.0 - draft 26, 12 | http://openid.net/specs/openid-connect-basic-1_0.html 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | Ada Security provides a security framework which allows applications to define 4 | and enforce security policies. This framework allows users to authenticate by using 5 | [OpenID Authentication 2.0](http://openid.net/specs/openid-authentication-2_0.html) 6 | as well as [OAuth 2.0](http://oauth.net/2/) protocol. 7 | It allows a web application to integrate easily with Yahoo!, Facebook and 8 | Google+ authentication systems. 9 | The Ada05 library includes: 10 | 11 | * An OpenID client authentication, 12 | * An OAuth 2.0 client authentication, 13 | * An OpenID Connect authentication framework, 14 | * An OAuth 2.0 server authentication framework, 15 | * A policy based security framework to protect the resources 16 | 17 | ![Ada Security Overview](https://github.com/stcarrez/ada-security/wiki/images/AdaSecurity.jpg) 18 | 19 | 20 | -------------------------------------------------------------------------------- /security.gpr: -------------------------------------------------------------------------------- 1 | with "security_config"; 2 | with "utilada_sys"; 3 | with "utilada_xml"; 4 | 5 | library project Security is 6 | 7 | Version := "1.5.1"; 8 | Library_Dir := "lib"; 9 | Library_Type : Security_Config.Library_Type_Type := external ("SECURITY_LIBRARY_TYPE", "static"); 10 | 11 | for Source_Dirs use ("src"); 12 | for Library_Name use "security"; 13 | for Library_Kind use Library_Type; 14 | for Library_Version use "lib" & Project'Library_Name & ".so." & Version; 15 | 16 | for Library_Dir use Library_Dir & "/security/" & Project'Library_Kind; 17 | for Object_Dir use "./" & Security_Config'Object_Dir & "/obj/security/" & Project'Library_Kind; 18 | 19 | package Binder renames Security_Config.Binder; 20 | package Builder renames Security_Config.Builder; 21 | package Compiler renames Security_Config.Compiler; 22 | package Ide renames Security_Config.Ide; 23 | 24 | end Security; 25 | -------------------------------------------------------------------------------- /regtests/files/permissions/simple-policy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | developer 4 | 5 | 6 | manager 7 | 8 | 9 | admin 10 | 11 | 12 | admin 13 | admin 14 | manager 15 | 16 | 17 | anonymous 18 | 19 | 20 | logged-user 21 | 22 | 23 | admin 24 | /admin/.* 25 | 26 | 27 | logged-user 28 | /user/.* 29 | 30 | 31 | anonymous 32 | /.* 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/links.txt: -------------------------------------------------------------------------------- 1 | Ada Server Faces https://github.com/stcarrez/ada-asf 2 | Ada Database Objects https://github.com/stcarrez/ada-ado 3 | Ada Utility Library https://github.com/stcarrez/ada-util 4 | Ada Wiki https://github.com/stcarrez/ada-wiki 5 | Ada EL https://github.com/stcarrez/ada-el 6 | Ada Security https://github.com/stcarrez/ada-security 7 | AWS https://github.com/AdaCore/aws 8 | Dynamo https://github.com/stcarrez/dynamo 9 | Java Bean https://en.wikipedia.org/wiki/JavaBean 10 | Java Log4j https://logging.apache.org/log4j/2.x/ 11 | Log4cxx https://logging.apache.org/log4cxx/latest_stable/index.html 12 | RFC7231 https://tools.ietf.org/html/rfc7231 13 | RFC 6749 https://tools.ietf.org/html/rfc6749 14 | OpenID Authentication 2.0 https://openid.net/specs/openid-authentication-2_0.html 15 | OpenID Connect Core 1.0 https://openid.net/specs/openid-connect-core-1_0.html 16 | -------------------------------------------------------------------------------- /regtests/src/security-oauth-clients-tests.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security-oauth-clients-tests - Unit tests for OAuth 3 | -- Copyright (C) 2013 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Tests; 9 | 10 | package Security.OAuth.Clients.Tests is 11 | 12 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite); 13 | 14 | type Test is new Util.Tests.Test with null record; 15 | 16 | -- Test Create_Nonce operation. 17 | procedure Test_Create_Nonce (T : in out Test); 18 | 19 | -- Test the Get_State operation. 20 | procedure Test_Get_State (T : in out Test); 21 | 22 | -- Test the Is_Valid_State operation. 23 | procedure Test_Is_Valid_State (T : in out Test); 24 | 25 | -- Test the Get_Auth_Params operation. 26 | procedure Test_Get_Auth_Params (T : in out Test); 27 | 28 | end Security.OAuth.Clients.Tests; 29 | -------------------------------------------------------------------------------- /regtests/src/security-oauth-jwt-tests.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security-oayth-jwt-tests - Unit tests for JSON Web Token 3 | -- Copyright (C) 2013 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Tests; 9 | 10 | package Security.OAuth.JWT.Tests is 11 | 12 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite); 13 | 14 | type Test is new Util.Tests.Test with null record; 15 | 16 | -- Test Decode operation with errors. 17 | procedure Test_Decode_Error (T : in out Test); 18 | 19 | generic 20 | with function Get (From : in Token) return String; 21 | Value : String; 22 | procedure Test_Operation (T : in out Test); 23 | 24 | generic 25 | with function Get (From : in Token) return Ada.Calendar.Time; 26 | Value : String; 27 | procedure Test_Time_Operation (T : in out Test); 28 | 29 | end Security.OAuth.JWT.Tests; 30 | -------------------------------------------------------------------------------- /regtests/files/discover/yahoo.xrds: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://specs.openid.net/auth/2.0/server 9 | http://specs.openid.net/extensions/pape/1.0 10 | http://openid.net/srv/ax/1.0 11 | http://specs.openid.net/extensions/oauth/1.0 12 | http://specs.openid.net/extensions/ui/1.0/lang-pref 13 | http://specs.openid.net/extensions/ui/1.0/mode/popup 14 | http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier 15 | http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf 16 | http://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdf 17 | http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf 18 | https://open.login.yahooapis.com/openid/op/auth 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /regtests/files/permissions/policy-with-role.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | developer 4 | 5 | 6 | manager 7 | 8 | 9 | admin 10 | 11 | 12 | 13 | developer 14 | developer 15 | manager 16 | 17 | 18 | 19 | manager 20 | admin 21 | manager 22 | 23 | 24 | 25 | admin 26 | admin 27 | manager 28 | 29 | 30 | 31 | admin 32 | /admin/.* 33 | 34 | 35 | 36 | developer 37 | /developer/.* 38 | 39 | 40 | 41 | manager 42 | /manager/.* 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/security-auth-oauth-yahoo.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth-yahoo -- Yahoo! OAuth based authentication 3 | -- Copyright (C) 2013, 2014, 2020 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Security.OAuth.Clients; 9 | package Security.Auth.OAuth.Yahoo is 10 | 11 | -- ------------------------------ 12 | -- OAuth Manager 13 | -- ------------------------------ 14 | -- The Manager provides the core operations for the OAuth authorization process. 15 | type Manager is new Security.Auth.OAuth.Manager with null record; 16 | 17 | -- Verify the OAuth access token and retrieve information about the user. 18 | overriding 19 | procedure Verify_Access_Token (Realm : in Manager; 20 | Assoc : in Association; 21 | Request : in Parameters'Class; 22 | Token : in Security.OAuth.Clients.Access_Token_Access; 23 | Result : in out Authentication); 24 | 25 | end Security.Auth.OAuth.Yahoo; 26 | -------------------------------------------------------------------------------- /regtests/files/discover/verisign.xrds: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | http://specs.openid.net/auth/2.0/signon 10 | http://openid.net/sreg/1.0 11 | http://openid.net/extensions/sreg/1.1 12 | http://schemas.openid.net/pape/policies/2007/06/phishing-resistant 13 | http://schemas.openid.net/pape/policies/2007/06/multi-factor 14 | http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical 15 | http://pip.verisignlabs.com/server 16 | http://joe.pip.verisignlabs.com/ 17 | 18 | 19 | 20 | http://openid.net/signon/1.1 21 | http://openid.net/sreg/1.0 22 | http://openid.net/extensions/sreg/1.1 23 | http://pip.verisignlabs.com/server 24 | http://joe.pip.verisignlabs.com/ 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/security-auth-oauth-github.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth-github -- Github OAuth based authentication 3 | -- Copyright (C) 2013, 2014, 2020 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Security.OAuth.Clients; 9 | package Security.Auth.OAuth.Github is 10 | 11 | -- ------------------------------ 12 | -- OAuth Manager 13 | -- ------------------------------ 14 | -- The Manager provides the core operations for the OAuth authorization process. 15 | type Manager is new Security.Auth.OAuth.Manager with null record; 16 | 17 | -- Verify the OAuth access token and retrieve information about the user. 18 | overriding 19 | procedure Verify_Access_Token (Realm : in Manager; 20 | Assoc : in Association; 21 | Request : in Parameters'Class; 22 | Token : in Security.OAuth.Clients.Access_Token_Access; 23 | Result : in out Authentication); 24 | 25 | end Security.Auth.OAuth.Github; 26 | -------------------------------------------------------------------------------- /regtests/src/security-auth-tests.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-openid - Tests for OpenID 3 | -- Copyright (C) 2009, 2010, 2011, 2012, 2013 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Strings.Maps; 9 | with Util.Tests; 10 | package Security.Auth.Tests is 11 | 12 | use Ada.Strings.Unbounded; 13 | 14 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite); 15 | 16 | type Test is new Util.Tests.Test with null record; 17 | 18 | procedure Test_Discovery (T : in out Test); 19 | 20 | procedure Test_Verify_Signature (T : in out Test); 21 | 22 | type Test_Parameters is new Security.Auth.Parameters with record 23 | Params : Util.Strings.Maps.Map; 24 | end record; 25 | 26 | overriding 27 | function Get_Parameter (Params : in Test_Parameters; 28 | Name : in String) return String; 29 | 30 | procedure Set_Parameter (Params : in out Test_Parameters; 31 | Name : in String; 32 | Value : in String); 33 | 34 | end Security.Auth.Tests; 35 | -------------------------------------------------------------------------------- /regtests/src/security-testsuite.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security testsuite - Ada Security Test suite 3 | -- Copyright (C) 2011, 2012, 2013, 2017 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | with Security.Auth.Tests; 8 | with Security.Permissions.Tests; 9 | with Security.Policies.Tests; 10 | with Security.OAuth.JWT.Tests; 11 | with Security.OAuth.Clients.Tests; 12 | with Security.OAuth.Servers.Tests; 13 | with Security.Random.Tests; 14 | package body Security.Testsuite is 15 | 16 | Tests : aliased Util.Tests.Test_Suite; 17 | 18 | function Suite return Util.Tests.Access_Test_Suite is 19 | Ret : constant Util.Tests.Access_Test_Suite := Tests'Access; 20 | begin 21 | Security.Random.Tests.Add_Tests (Ret); 22 | Security.OAuth.JWT.Tests.Add_Tests (Ret); 23 | Security.Auth.Tests.Add_Tests (Ret); 24 | Security.Permissions.Tests.Add_Tests (Ret); 25 | Security.Policies.Tests.Add_Tests (Ret); 26 | Security.OAuth.Clients.Tests.Add_Tests (Ret); 27 | Security.OAuth.Servers.Tests.Add_Tests (Ret); 28 | return Ret; 29 | end Suite; 30 | 31 | end Security.Testsuite; 32 | -------------------------------------------------------------------------------- /regtests/files/permissions/grants.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | developer 4 | 5 | 6 | manager 7 | 8 | 9 | admin 10 | 11 | 12 | 13 | create-ticket 14 | developer 15 | manager 16 | 17 | 18 | 19 | update-ticket 20 | developer 21 | manager 22 | 23 | 24 | 25 | delete-ticket 26 | developer 27 | manager 28 | 29 | 30 | 31 | add-user 32 | admin 33 | manager 34 | 35 | 36 | 37 | remove-user 38 | admin 39 | manager 40 | 41 | 42 | 43 | admin 44 | /admin/.* 45 | 46 | 47 | 48 | create-ticket 49 | /developer/.* 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/security-controllers-urls.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-controllers-urls -- URL permission controller 3 | -- Copyright (C) 2012 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | package body Security.Controllers.URLs is 9 | 10 | -- ------------------------------ 11 | -- Returns true if the user associated with the security context Context has 12 | -- the permission to access the URL defined in Permission. 13 | -- ------------------------------ 14 | overriding 15 | function Has_Permission (Handler : in URL_Controller; 16 | Context : in Security.Contexts.Security_Context'Class; 17 | Permission : in Security.Permissions.Permission'Class) 18 | return Boolean is 19 | begin 20 | if Permission in Security.Policies.URLs.URL_Permission'Class then 21 | return Handler.Manager.Has_Permission (Context, 22 | Policies.URLs.URL_Permission'Class (Permission)); 23 | else 24 | return False; 25 | end if; 26 | end Has_Permission; 27 | 28 | end Security.Controllers.URLs; 29 | -------------------------------------------------------------------------------- /regtests/src/security-oauth-servers-tests.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security-oauth-servers-tests - Unit tests for server side OAuth 3 | -- Copyright (C) 2017, 2018 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Tests; 9 | 10 | package Security.OAuth.Servers.Tests is 11 | 12 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite); 13 | 14 | type Test is new Util.Tests.Test with null record; 15 | 16 | -- Test the application manager. 17 | procedure Test_Application_Manager (T : in out Test); 18 | 19 | -- Test the user registration and verification. 20 | procedure Test_User_Verify (T : in out Test); 21 | 22 | -- Test the token operation that produces an access token from user/password. 23 | -- RFC 6749: Section 4.3. Resource Owner Password Credentials Grant 24 | procedure Test_Token_Password (T : in out Test); 25 | 26 | -- Test the access token validation with invalid tokens (bad formed). 27 | procedure Test_Bad_Token (T : in out Test); 28 | 29 | -- Test the loading configuration files for the File_Registry. 30 | procedure Test_Load_Registry (T : in out Test); 31 | 32 | end Security.OAuth.Servers.Tests; 33 | -------------------------------------------------------------------------------- /regtests/src/security-permissions-tests.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security-permissions-tests - Unit tests for Security.Permissions 3 | -- Copyright (C) 2011, 2012, 2016 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Tests; 9 | 10 | package Security.Permissions.Tests is 11 | 12 | package P_Admin is new Permissions.Definition ("admin"); 13 | package P_Create is new Permissions.Definition ("create"); 14 | package P_Update is new Permissions.Definition ("update"); 15 | package P_Delete is new Permissions.Definition ("delete"); 16 | 17 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite); 18 | 19 | type Test is new Util.Tests.Test with null record; 20 | 21 | -- Test Add_Permission and Get_Permission_Index. 22 | procedure Test_Add_Permission (T : in out Test); 23 | 24 | -- Test the permission created by the Definition package. 25 | procedure Test_Define_Permission (T : in out Test); 26 | 27 | -- Test Get_Permission on invalid permission name. 28 | procedure Test_Get_Invalid_Permission (T : in out Test); 29 | 30 | -- Test operations on the Permission_Index_Set. 31 | procedure Test_Add_Permission_Set (T : in out Test); 32 | 33 | end Security.Permissions.Tests; 34 | -------------------------------------------------------------------------------- /src/security-controllers-urls.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-controllers-urls -- URL permission controller 3 | -- Copyright (C) 2012 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Security.Contexts; 9 | with Security.Permissions; 10 | 11 | with Security.Policies.URLs; 12 | 13 | package Security.Controllers.URLs is 14 | 15 | -- ------------------------------ 16 | -- Security Controller 17 | -- ------------------------------ 18 | -- The URL_Controller implements the permission check for URL permissions. 19 | -- It uses the URL policy manager to verify the permission. 20 | type URL_Controller is limited new Controller with record 21 | Manager : Security.Policies.URLs.URL_Policy_Access; 22 | end record; 23 | type URL_Controller_Access is access all URL_Controller'Class; 24 | 25 | -- Returns true if the user associated with the security context Context has 26 | -- the permission to access the URL defined in Permission. 27 | overriding 28 | function Has_Permission (Handler : in URL_Controller; 29 | Context : in Security.Contexts.Security_Context'Class; 30 | Permission : in Security.Permissions.Permission'Class) 31 | return Boolean; 32 | 33 | end Security.Controllers.URLs; 34 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=security 2 | VERSION=1.5.2 3 | 4 | DIST_DIR=ada-security-$(VERSION) 5 | DIST_FILE=ada-security-$(VERSION).tar.gz 6 | 7 | MAKE_ARGS += -XSECURITY_BUILD=$(BUILD) 8 | 9 | -include Makefile.conf 10 | 11 | STATIC_MAKE_ARGS = $(MAKE_ARGS) -XSECURITY_LIBRARY_TYPE=static 12 | SHARED_MAKE_ARGS = $(MAKE_ARGS) -XSECURITY_LIBRARY_TYPE=relocatable 13 | SHARED_MAKE_ARGS += -XUTILADA_BASE_BUILD=relocatable -XUTIL_LIBRARY_TYPE=relocatable 14 | SHARED_MAKE_ARGS += -XXMLADA_BUILD=relocatable 15 | SHARED_MAKE_ARGS += -XLIBRARY_TYPE=relocatable 16 | 17 | include Makefile.defaults 18 | 19 | build-test:: lib-setup 20 | cd regtests && $(BUILD_COMMAND) $(GPRFLAGS) $(MAKE_ARGS) 21 | 22 | # Build and run the unit tests 23 | test: build 24 | bin/security_harness -l $(NAME): -xml security-aunit.xml 25 | 26 | samples: 27 | cd samples && $(BUILD_COMMAND) $(GPRFLAGS) $(MAKE_ARGS) 28 | 29 | SECURITY_DOC= \ 30 | title.md \ 31 | pagebreak.tex \ 32 | index.md \ 33 | pagebreak.tex \ 34 | Installation.md \ 35 | pagebreak.tex \ 36 | Security.md \ 37 | pagebreak.tex \ 38 | Security_Auth.md \ 39 | pagebreak.tex \ 40 | Security_OAuth.md \ 41 | pagebreak.tex \ 42 | Security_Policies.md 43 | 44 | DOC_OPTIONS=-f markdown --listings --number-sections --toc 45 | HTML_OPTIONS=-f markdown --listings --number-sections --toc --css pandoc.css 46 | 47 | $(eval $(call ada_library,$(NAME),.)) 48 | $(eval $(call pandoc_build,security-book,$(SECURITY_DOC))) 49 | $(eval $(call alire_publish,alire.toml,se/security,security-$(VERSION).toml)) 50 | 51 | .PHONY: samples 52 | -------------------------------------------------------------------------------- /samples/web/success.thtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Success 9 | 10 |
11 |
12 |
13 | 20 |
21 |
22 |
23 | 24 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/security-auth-oauth-googleplus.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth-googleplus -- Google+ OAuth based authentication 3 | -- Copyright (C) 2013 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Security.OAuth.Clients; 9 | 10 | -- === Google+ === 11 | -- The Google+ authentication is based on OAuth 2.0 and the OpenID Connect Basic Client Profile. 12 | -- 13 | -- See https://developers.google.com/accounts/docs/OAuth2Login 14 | package Security.Auth.OAuth.Googleplus is 15 | 16 | -- ------------------------------ 17 | -- OAuth Manager 18 | -- ------------------------------ 19 | -- The Manager provides the core operations for the OAuth authorization process. 20 | type Manager is new Security.Auth.OAuth.Manager with private; 21 | 22 | -- Verify the OAuth access token and retrieve information about the user. 23 | overriding 24 | procedure Verify_Access_Token (Realm : in Manager; 25 | Assoc : in Association; 26 | Request : in Parameters'Class; 27 | Token : in Security.OAuth.Clients.Access_Token_Access; 28 | Result : in out Authentication); 29 | 30 | private 31 | 32 | type Manager is new Security.Auth.OAuth.Manager with null record; 33 | 34 | end Security.Auth.OAuth.Googleplus; 35 | -------------------------------------------------------------------------------- /src/security-controllers-roles.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-controllers-roles -- Simple role base security 3 | -- Copyright (C) 2011, 2012 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Security.Contexts; 9 | with Security.Permissions; 10 | 11 | with Security.Policies.Roles; 12 | 13 | package Security.Controllers.Roles is 14 | 15 | -- ------------------------------ 16 | -- Security Controller 17 | -- ------------------------------ 18 | -- The Role_Controller implements a simple role based permissions check. 19 | -- The permission is granted if the user has the role defined by the controller. 20 | type Role_Controller (Count : Positive) is limited new Controller with record 21 | Roles : Policies.Roles.Role_Type_Array (1 .. Count); 22 | end record; 23 | type Role_Controller_Access is access all Role_Controller'Class; 24 | 25 | -- Returns true if the user associated with the security context Context has 26 | -- one of the role defined in the Handler. 27 | overriding 28 | function Has_Permission (Handler : in Role_Controller; 29 | Context : in Security.Contexts.Security_Context'Class; 30 | Permission : in Security.Permissions.Permission'Class) 31 | return Boolean; 32 | 33 | end Security.Controllers.Roles; 34 | -------------------------------------------------------------------------------- /regtests/files/discover/myopenid.xrds: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | http://specs.openid.net/auth/2.0/signon 9 | http://openid.net/sreg/1.0 10 | http://openid.net/extensions/sreg/1.1 11 | http://schemas.openid.net/pape/policies/2007/06/phishing-resistant 12 | http://openid.net/srv/ax/1.0 13 | http://www.myopenid.com/server 14 | http://joe.myopenid.com/ 15 | 16 | 17 | http://openid.net/signon/1.1 18 | http://openid.net/sreg/1.0 19 | http://openid.net/extensions/sreg/1.1 20 | http://schemas.openid.net/pape/policies/2007/06/phishing-resistant 21 | http://openid.net/srv/ax/1.0 22 | http://www.myopenid.com/server 23 | http://joe.myopenid.com/ 24 | 25 | 26 | http://openid.net/signon/1.0 27 | http://openid.net/sreg/1.0 28 | http://openid.net/extensions/sreg/1.1 29 | http://schemas.openid.net/pape/policies/2007/06/phishing-resistant 30 | http://openid.net/srv/ax/1.0 31 | http://www.myopenid.com/server 32 | http://joe.myopenid.com/ 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/security-auth-oauth-facebook.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth-facebook -- Facebook OAuth based authentication 3 | -- Copyright (C) 2013, 2014 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Security.OAuth.Clients; 9 | package Security.Auth.OAuth.Facebook is 10 | 11 | -- ------------------------------ 12 | -- OAuth Manager 13 | -- ------------------------------ 14 | -- The Manager provides the core operations for the OAuth authorization process. 15 | type Manager is new Security.Auth.OAuth.Manager with private; 16 | 17 | -- Initialize the authentication realm. 18 | overriding 19 | procedure Initialize (Realm : in out Manager; 20 | Params : in Parameters'Class; 21 | Provider : in String := PROVIDER_OPENID); 22 | 23 | -- Verify the OAuth access token and retrieve information about the user. 24 | overriding 25 | procedure Verify_Access_Token (Realm : in Manager; 26 | Assoc : in Association; 27 | Request : in Parameters'Class; 28 | Token : in Security.OAuth.Clients.Access_Token_Access; 29 | Result : in out Authentication); 30 | 31 | private 32 | 33 | type Manager is new Security.Auth.OAuth.Manager with record 34 | App_Access_Token : Ada.Strings.Unbounded.Unbounded_String; 35 | end record; 36 | 37 | end Security.Auth.OAuth.Facebook; 38 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | Version 1.5.2 - Under development 2 | - Fix #11: Ada.Calendar.Conversions.To_Struct_Timespec is deprecated 3 | 4 | Version 1.5.1 - Aug 2024 5 | - Cleanup build environment to drop configure 6 | 7 | Version 1.5.0 - Jul 2023 8 | - Add Create function for API key grant types 9 | 10 | Version 1.4.1 - Aug 2022 11 | - Fix Alire GNAT project to build in debug mode 12 | - Fix Security.Random that generates shorter random string 13 | 14 | Version 1.4.0 - Dec 2020 15 | - Add support to authenticate with Gitlab and GitHub 16 | - Update Yahoo! authenticate to use OpenId connect 17 | - Update the AWS demo to add Gitlab and Github 18 | 19 | Version 1.3.1 - Nov 2020 20 | - Fix AWS example 21 | 22 | Version 1.3.0 - May 2020 23 | - Add support to extend the authenticate manager and allow to 24 | use custom authentication through the Set_Default_Factory operation. 25 | 26 | Version 1.2.1 - Dec 2019 27 | - Cleanup the build process 28 | 29 | Version 1.2.0 - Jul 2018 30 | - OAuth 2.0 server implementation (RFC 6749) 31 | - Improvement of the role based security policy 32 | 33 | Version 1.1.2 - Dec 2015 34 | - Improvement of configure and installation process with gprinstall (if available) 35 | 36 | Version 1.1.1 - Jul 2014 37 | - Fix minor configuration issue with GNAT 2014 38 | 39 | Version 1.1 - Mar 2014 40 | - New authentication framework that supports OpenID, OpenID Connect, OAuth, Facebook login 41 | - AWS demo for a Google, Yahoo!, Facebook, Google+ authentication 42 | - Support to extract JSON Web Token (JWT) 43 | - Support for the creation of Debian packages 44 | 45 | Version 1.0 - Jan 2013 46 | - OpenID implementation based on Ada Server Faces 47 | - OAuth 2.0 client implementation 48 | - Security policy core implementation 49 | -------------------------------------------------------------------------------- /docs/Installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | This chapter explains how to build and install the library. 4 | 5 | ## Using Alire 6 | 7 | To use Ada Security in your project, run the following command to add the dependency 8 | for your project: 9 | 10 | ``` 11 | alr index --update-all 12 | alr with security 13 | ``` 14 | 15 | ## Without Alire 16 | 17 | If you don't have [Alire](https://alire.ada.dev/) or want to build and install the library 18 | on a specific place, run a `setup` command to configure the build as well as installation 19 | directory. 20 | 21 | The `HAVE_ALIRE` configuration allows you to disable the build with [Alire](https://alire.ada.dev/): 22 | 23 | ``` 24 | make setup BUILD=debug PREFIX=/build/install HAVE_ALIRE=no 25 | ``` 26 | 27 | Since this build method does not verify that all dependencies are met, make sure that you 28 | have already built and install the following components and they are available to `gprbuild` 29 | through `ADA_PROJECT_PATH` if needed: 30 | 31 | * [Ada Utility Library](https://gitlab.com/stcarrez/ada-util/) 32 | 33 | Then build, run the unit tests and install by using: 34 | 35 | ``` 36 | make 37 | make test 38 | make install 39 | ``` 40 | 41 | To use the installed libraries, make sure your `ADA_PROJECT_PATH` contains the directory 42 | where you installed the libraries (configured by the `PREFIX=` option in the setup phase). 43 | The installed GNAT projects are the same as those used when using [Alire](https://alire.ada.dev/). 44 | 45 | If you want to install on a specific place, you can change the `prefix` and indicate the installation 46 | direction as follows: 47 | 48 | ``` 49 | make install prefix=/opt 50 | ``` 51 | 52 | ## Using 53 | 54 | To use the library in an Ada project, add the following line at the beginning of your GNAT project file: 55 | 56 | ``` 57 | with "security"; 58 | ``` 59 | -------------------------------------------------------------------------------- /src/security-controllers-roles.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-controllers-roles -- Simple role base security 3 | -- Copyright (C) 2011, 2012, 2018 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | package body Security.Controllers.Roles is 9 | 10 | -- ------------------------------ 11 | -- Returns true if the user associated with the security context Context has 12 | -- one of the role defined in the Handler. 13 | -- ------------------------------ 14 | overriding 15 | function Has_Permission (Handler : in Role_Controller; 16 | Context : in Security.Contexts.Security_Context'Class; 17 | Permission : in Security.Permissions.Permission'Class) 18 | return Boolean is 19 | pragma Unreferenced (Permission); 20 | 21 | P : constant Security.Principal_Access := Context.Get_User_Principal; 22 | Roles : Security.Policies.Roles.Role_Map; 23 | begin 24 | if P /= null then 25 | -- If the principal has some roles, get them. 26 | if P.all in Policies.Roles.Role_Principal_Context'Class then 27 | Roles := Policies.Roles.Role_Principal_Context'Class (P.all).Get_Roles; 28 | else 29 | return False; 30 | end if; 31 | 32 | for I in Handler.Roles'Range loop 33 | if Roles (Handler.Roles (I)) then 34 | return True; 35 | end if; 36 | end loop; 37 | end if; 38 | return False; 39 | end Has_Permission; 40 | 41 | end Security.Controllers.Roles; 42 | -------------------------------------------------------------------------------- /src/security-controllers.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-controllers -- Controllers to verify a security permission 3 | -- Copyright (C) 2011, 2012 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Security.Contexts; 9 | with Security.Permissions; 10 | 11 | -- == Security Controller == 12 | -- The Security.Controllers package defines the security controller used to 13 | -- verify that a given permission is granted. A security controller uses the security 14 | -- context and other controller specific and internal data to verify that the permission 15 | -- is granted. 16 | -- 17 | -- Security controller instances are created when the security policy rules are parsed. 18 | -- These instances are shared across possibly several concurrent requests. 19 | package Security.Controllers is 20 | 21 | Invalid_Controller : exception; 22 | 23 | -- ------------------------------ 24 | -- Security Controller interface 25 | -- ------------------------------ 26 | type Controller is limited interface; 27 | type Controller_Access is access all Controller'Class; 28 | 29 | -- Checks whether the permission defined by the Handler controller data is granted 30 | -- by the security context passed in Context. 31 | -- Returns true if such permission is granted. 32 | function Has_Permission (Handler : in Controller; 33 | Context : in Security.Contexts.Security_Context'Class; 34 | Permission : in Security.Permissions.Permission'Class) 35 | return Boolean is abstract; 36 | 37 | end Security.Controllers; 38 | -------------------------------------------------------------------------------- /samples/src/auth_cb.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- auth_cb -- Authentication callback examples 3 | -- Copyright (C) 2013 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with AWS.Response; 9 | with AWS.Status; 10 | with Util.Properties; 11 | with Security.Auth; 12 | package Auth_CB is 13 | 14 | -- Implement the first step of authentication: discover the OpenID (if any) provider, 15 | -- create the authorization request and redirect the user to the authorization server. 16 | -- Some authorization data is saved in the session for the verify process. 17 | -- The authorization provider is defined according to the base URI which is configured 18 | -- by the Auth_Config properties. The following names are recognized: 19 | -- 20 | -- google yahoo orange facebook 21 | -- 22 | -- Each authorization provider has its own set of configuration parameter which defines 23 | -- what implementation to use (OpenID or OAuth) and how to configure and connect 24 | -- to the server. The google, yahoo and orange map to the openid implementation. 25 | -- The facebook maps to the facebook implementation (based on OAuth). 26 | 27 | function Get_Authorization (Request : in AWS.Status.Data) return AWS.Response.Data; 28 | 29 | -- Second step of authentication: verify the authorization response. The authorization 30 | -- data saved in the session is extracted and checked against the response. If it matches 31 | -- the response is verified to check if the authentication succeeded or not. 32 | -- The user is then redirected to the success page. 33 | function Verify_Authorization (Request : in AWS.Status.Data) return AWS.Response.Data; 34 | 35 | -- Display information about the current user. 36 | function User_Info (Request : in AWS.Status.Data) return AWS.Response.Data; 37 | 38 | type Auth_Config is new Util.Properties.Manager and Security.Auth.Parameters with null record; 39 | 40 | overriding 41 | function Get_Parameter (Params : in Auth_Config; 42 | Name : in String) return String; 43 | 44 | Config : Auth_Config; 45 | 46 | end Auth_CB; 47 | -------------------------------------------------------------------------------- /security_config.gpr: -------------------------------------------------------------------------------- 1 | abstract project Security_Config is 2 | for Source_Dirs use (); 3 | 4 | type Yes_No is ("yes", "no"); 5 | 6 | type Library_Type_Type is ("relocatable", "static", "static-pic"); 7 | 8 | type Build_Type is ("distrib", "debug", "optimize", "profile", "coverage"); 9 | Mode : Build_Type := external ("SECURITY_BUILD", "debug"); 10 | 11 | Processors := External ("PROCESSORS", "0"); 12 | 13 | package Builder is 14 | for Default_Switches ("Ada") use ("-j" & Processors); 15 | end Builder; 16 | 17 | package compiler is 18 | warnings := ("-gnatwua"); 19 | defaults := ("-gnat2012"); 20 | case Mode is 21 | when "distrib" => 22 | for Default_Switches ("Ada") use defaults & ("-O2", "-gnatafno", "-gnatVa", "-gnatwa"); 23 | 24 | when "debug" => 25 | for Default_Switches ("Ada") use defaults & warnings 26 | & ("-g", "-gnata", "-gnatVaMI", "-gnaty3abcefhiklmnprstxM99"); 27 | 28 | when "coverage" => 29 | for Default_Switches ("Ada") use defaults & warnings 30 | & ("-g", "-O2", "-gnata", "-gnatVaMI", "-gnaty3abcefhiklmnprstxM99", 31 | "-fprofile-arcs", "-ftest-coverage"); 32 | 33 | when "optimize" => 34 | for Default_Switches ("Ada") use defaults & warnings 35 | & ("-O2", "-gnatn", "-gnatp", "-fdata-sections", "-ffunction-sections"); 36 | 37 | when "profile" => 38 | for Default_Switches ("Ada") use defaults & warnings & ("-pg"); 39 | 40 | end case; 41 | end compiler; 42 | 43 | package binder is 44 | case Mode is 45 | when "debug" => 46 | for Default_Switches ("Ada") use ("-E"); 47 | 48 | when others => 49 | for Default_Switches ("Ada") use ("-E"); 50 | 51 | end case; 52 | end binder; 53 | 54 | package linker is 55 | case Mode is 56 | when "profile" => 57 | for Default_Switches ("Ada") use ("-pg"); 58 | 59 | when "distrib" => 60 | for Default_Switches ("Ada") use ("-s"); 61 | 62 | when "optimize" => 63 | for Default_Switches ("Ada") use ("-Wl,--gc-sections"); 64 | 65 | when "coverage" => 66 | for Default_Switches ("ada") use ("-fprofile-arcs"); 67 | 68 | when others => 69 | null; 70 | end case; 71 | 72 | end linker; 73 | 74 | package Ide is 75 | for VCS_Kind use "git"; 76 | end Ide; 77 | 78 | end Security_Config; 79 | -------------------------------------------------------------------------------- /src/security-auth-oauth-github.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth-github -- Github OAuth based authentication 3 | -- Copyright (C) 2020, 2021 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Log.Loggers; 9 | with Util.Http.Clients; 10 | with Util.Properties.JSON; 11 | package body Security.Auth.OAuth.Github is 12 | 13 | use Util.Log; 14 | 15 | Log : constant Loggers.Logger := Util.Log.Loggers.Create ("Security.Auth.OAuth.Github"); 16 | 17 | -- ------------------------------ 18 | -- Verify the OAuth access token and retrieve information about the user. 19 | -- ------------------------------ 20 | overriding 21 | procedure Verify_Access_Token (Realm : in Manager; 22 | Assoc : in Association; 23 | Request : in Parameters'Class; 24 | Token : in Security.OAuth.Clients.Access_Token_Access; 25 | Result : in out Authentication) is 26 | pragma Unreferenced (Assoc, Request); 27 | 28 | URI : constant String := "https://api.github.com/user"; 29 | Http : Util.Http.Clients.Client; 30 | Reply : Util.Http.Clients.Response; 31 | Props : Util.Properties.Manager; 32 | begin 33 | Http.Add_Header ("Authorization", "token " & Token.Get_Name); 34 | Http.Get (URI, Reply); 35 | if Reply.Get_Status /= Util.Http.SC_OK then 36 | Log.Warn ("Cannot retrieve Github user information"); 37 | Set_Result (Result, INVALID_SIGNATURE, "invalid access token"); 38 | return; 39 | end if; 40 | 41 | Util.Properties.JSON.Parse_JSON (Props, Reply.Get_Body); 42 | 43 | Result.Identity := Realm.Issuer; 44 | Append (Result.Identity, "/"); 45 | Append (Result.Identity, String '(Props.Get ("id"))); 46 | Result.Claimed_Id := Result.Identity; 47 | Result.First_Name := To_Unbounded_String (Props.Get ("name")); 48 | Result.Full_Name := To_Unbounded_String (Props.Get ("name")); 49 | 50 | -- The email is optional and depends on the scope. 51 | Result.Email := To_Unbounded_String (Props.Get ("email")); 52 | Set_Result (Result, AUTHENTICATED, "authenticated"); 53 | end Verify_Access_Token; 54 | 55 | end Security.Auth.OAuth.Github; 56 | -------------------------------------------------------------------------------- /alire.toml: -------------------------------------------------------------------------------- 1 | description = "Security Library for HTTP client and server with OAuth2 support" 2 | name = "security" 3 | version = "1.5.2" 4 | licenses = "Apache-2.0" 5 | maintainers = ["Stephane.Carrez@gmail.com"] 6 | maintainers-logins = ["stcarrez"] 7 | authors = ["Stephane.Carrez@gmail.com"] 8 | project-files = ["security.gpr"] 9 | tags = ["security", "oauth2", "authentication", "permissions", "jwt"] 10 | website = "https://gitlab.com/stcarrez/ada-security" 11 | long-description = """ 12 | 13 | [![Build Status](https://img.shields.io/endpoint?url=https://porion.vacs.fr/porion/api/v1/projects/ada-security/badges/build.json)](https://porion.vacs.fr/porion/projects/view/ada-security/summary) 14 | [![Test Status](https://img.shields.io/endpoint?url=https://porion.vacs.fr/porion/api/v1/projects/ada-security/badges/tests.json)](https://porion.vacs.fr/porion/projects/view/ada-securit/xunits) 15 | [![Coverage](https://img.shields.io/endpoint?url=https://porion.vacs.fr/porion/api/v1/projects/ada-security/badges/coverage.json)](https://porion.vacs.fr/porion/projects/view/ada-security/summary) 16 | [![Documentation Status](https://readthedocs.org/projects/ada-security/badge/?version=latest)](https://ada-security.readthedocs.io/en/latest/?badge=latest) 17 | 18 | Ada Security provides a security framework which allows applications to define 19 | and enforce security policies. This framework allows users to authenticate by using 20 | [OpenID Authentication 2.0](https://openid.net/specs/openid-authentication-2_0.html) 21 | as well as [OAuth 2.0](https://oauth.net/2/) protocol. 22 | It allows a web application to integrate easily with Yahoo!, Gitlab, Github, Facebook and 23 | Google+ authentication systems. 24 | The Ada05 library includes: 25 | 26 | * An OpenID client authentication, 27 | * An OAuth 2.0 client authentication, 28 | * An OpenID Connect authentication framework, 29 | * An OAuth 2.0 server authentication framework, 30 | * A policy based security framework to protect the resources 31 | 32 | The Ada Security library is used by the 33 | [Ada Web Application](https://gitlab.com/stcarrez/ada-awa) 34 | to provide authentication and access control to users within the web applications. 35 | 36 | ## Documentation 37 | 38 | * [Ada Security Programmer's Guide](https://ada-security.readthedocs.io/en/latest/) 39 | 40 | """ 41 | 42 | [[depends-on]] 43 | utilada = "^2.8.0" 44 | utilada_xml = "^2.8.0" 45 | 46 | [gpr-externals] 47 | SECURITY_BUILD = ["distrib", "debug", "optimize", "profile", "coverage"] 48 | SECURITY_LIBRARY_TYPE = ["relocatable", "static", "static-pic"] 49 | 50 | [configuration] 51 | disabled = true 52 | -------------------------------------------------------------------------------- /src/security-oauth.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-oauth -- OAuth Security 3 | -- Copyright (C) 2017, 2018 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | package body Security.OAuth is 8 | 9 | use Ada.Strings.Unbounded; 10 | 11 | -- ------------------------------ 12 | -- Get the application identifier. 13 | -- ------------------------------ 14 | function Get_Application_Identifier (App : in Application) return String is 15 | begin 16 | return To_String (App.Client_Id); 17 | end Get_Application_Identifier; 18 | 19 | -- ------------------------------ 20 | -- Set the application identifier used by the OAuth authorization server 21 | -- to identify the application (for example, the App ID in Facebook). 22 | -- ------------------------------ 23 | procedure Set_Application_Identifier (App : in out Application; 24 | Client : in String) is 25 | begin 26 | App.Client_Id := To_Unbounded_String (Client); 27 | end Set_Application_Identifier; 28 | 29 | -- ------------------------------ 30 | -- Set the application secret defined in the OAuth authorization server 31 | -- for the application (for example, the App Secret in Facebook). 32 | -- ------------------------------ 33 | procedure Set_Application_Secret (App : in out Application; 34 | Secret : in String) is 35 | begin 36 | App.Secret := To_Unbounded_String (Secret); 37 | end Set_Application_Secret; 38 | 39 | -- ------------------------------ 40 | -- Set the redirection callback that will be used to redirect the user 41 | -- back to the application after the OAuth authorization is finished. 42 | -- ------------------------------ 43 | procedure Set_Application_Callback (App : in out Application; 44 | URI : in String) is 45 | begin 46 | App.Callback := To_Unbounded_String (URI); 47 | end Set_Application_Callback; 48 | 49 | -- ------------------------------ 50 | -- Set the client authentication method used when doing OAuth calls for this application. 51 | -- See RFC 6749, 2.3. Client Authentication 52 | -- ------------------------------ 53 | procedure Set_Client_Authentication (App : in out Application; 54 | Method : in Client_Authentication_Type) is 55 | begin 56 | App.Client_Auth := Method; 57 | end Set_Client_Authentication; 58 | 59 | end Security.OAuth; 60 | -------------------------------------------------------------------------------- /samples/web/atlas/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Log in 10 | 11 | 12 | 13 |
14 |
15 |
16 | 51 | 52 | 53 |
54 |
55 |
56 |
57 | 58 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /regtests/src/security-policies-tests.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security-policies-tests - Unit tests for Security.Permissions 3 | -- Copyright (C) 2011, 2012, 2013, 2017, 2022 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Tests; 9 | with Util.Strings; 10 | 11 | with Security.Policies.Roles; 12 | 13 | package Security.Policies.Tests is 14 | 15 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite); 16 | 17 | type Test is new Util.Tests.Test with null record; 18 | 19 | -- Test Create_Role and Get_Role_Name 20 | procedure Test_Create_Role (T : in out Test); 21 | 22 | -- Test Set_Roles 23 | procedure Test_Set_Roles (T : in out Test); 24 | 25 | -- Test Set_Roles on an invalid role name 26 | procedure Test_Set_Invalid_Roles (T : in out Test); 27 | 28 | -- Test the Get_Policy, Get_Role_Policy and Add_Policy operations. 29 | procedure Test_Get_Role_Policy (T : in out Test); 30 | 31 | -- Test Has_Permission 32 | procedure Test_Has_Permission (T : in out Test); 33 | 34 | -- Test reading an empty policy file 35 | procedure Test_Read_Empty_Policy (T : in out Test); 36 | 37 | -- Test reading policy files 38 | procedure Test_Read_Policy (T : in out Test); 39 | 40 | -- Test reading policy files and using the controller 41 | procedure Test_Role_Policy (T : in out Test); 42 | 43 | -- Test the Get_Grants operation. 44 | procedure Test_Grants (T : in out Test); 45 | 46 | -- Test anonymous users and permissions. 47 | procedure Test_Anonymous_Permission (T : in out Test); 48 | 49 | -- Read the policy file File and perform a test on the given URI 50 | -- with a user having the given role. 51 | procedure Check_Policy (T : in out Test; 52 | File : in String; 53 | Role : in String; 54 | URL : in String; 55 | Anonymous : in Boolean := False; 56 | Granted : in Boolean := True); 57 | 58 | type Test_Principal is new Principal and Roles.Role_Principal_Context with record 59 | Name : Util.Strings.String_Ref; 60 | Roles : Security.Policies.Roles.Role_Map := (others => False); 61 | end record; 62 | 63 | -- Get the roles assigned to the user. 64 | overriding 65 | function Get_Roles (User : in Test_Principal) return Roles.Role_Map; 66 | 67 | -- Get the principal name. 68 | overriding 69 | function Get_Name (From : in Test_Principal) return String; 70 | 71 | end Security.Policies.Tests; 72 | -------------------------------------------------------------------------------- /src/security-random.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-random -- Random numbers for nonce, secret keys, token generation 3 | -- Copyright (C) 2017, 2023 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | with Ada.Streams; 8 | with Ada.Finalization; 9 | with Ada.Strings.Unbounded; 10 | private with Ada.Numerics.Discrete_Random; 11 | private with Interfaces; 12 | 13 | -- == Random Generator == 14 | -- The Security.Random package defines the Generator tagged type 15 | -- which provides operations to generate random tokens intended to be used for 16 | -- a nonce, access token, salt or other purposes. The generator is intended to be 17 | -- used in multi-task environments as it implements the low level random generation 18 | -- within a protected type. The generator defines a Generate operation 19 | -- that returns either a binary random array or the base64url encoding of the 20 | -- binary array. 21 | package Security.Random is 22 | 23 | type Generator is limited new Ada.Finalization.Limited_Controlled with private; 24 | 25 | -- Initialize the random generator. 26 | overriding 27 | procedure Initialize (Gen : in out Generator); 28 | 29 | -- Fill the array with pseudo-random numbers. 30 | procedure Generate (Gen : in out Generator; 31 | Into : out Ada.Streams.Stream_Element_Array); 32 | procedure Generate (Gen : in out Generator; 33 | Into : out String); 34 | 35 | -- Generate a random sequence of bits and convert the result 36 | -- into a string in base64url. 37 | function Generate (Gen : in out Generator'Class; 38 | Bits : in Positive) return String; 39 | 40 | -- Generate a random sequence of bits, convert the result 41 | -- into a string in base64url and append it to the buffer. 42 | procedure Generate (Gen : in out Generator'Class; 43 | Bits : in Positive; 44 | Into : in out Ada.Strings.Unbounded.Unbounded_String); 45 | 46 | private 47 | 48 | package Id_Random is new Ada.Numerics.Discrete_Random (Interfaces.Unsigned_32); 49 | 50 | -- Protected type to allow using the random generator by several tasks. 51 | protected type Raw_Generator is 52 | 53 | procedure Generate (Into : out Ada.Streams.Stream_Element_Array); 54 | 55 | procedure Reset; 56 | private 57 | -- Random number generator used for ID generation. 58 | Rand : Id_Random.Generator; 59 | end Raw_Generator; 60 | 61 | type Generator is limited new Ada.Finalization.Limited_Controlled with record 62 | Rand : Raw_Generator; 63 | end record; 64 | 65 | end Security.Random; 66 | -------------------------------------------------------------------------------- /samples/web/atlas/css/grids/reset.css: -------------------------------------------------------------------------------- 1 | /* `XHTML, HTML4, HTML5 Reset 2 | ----------------------------------------------------------------------------------------------------*/ 3 | 4 | a, 5 | abbr, 6 | acronym, 7 | address, 8 | applet, 9 | article, 10 | aside, 11 | audio, 12 | b, 13 | big, 14 | blockquote, 15 | body, 16 | canvas, 17 | caption, 18 | center, 19 | cite, 20 | code, 21 | dd, 22 | del, 23 | details, 24 | dfn, 25 | dialog, 26 | div, 27 | dl, 28 | dt, 29 | em, 30 | embed, 31 | fieldset, 32 | figcaption, 33 | figure, 34 | font, 35 | footer, 36 | form, 37 | h1, 38 | h2, 39 | h3, 40 | h4, 41 | h5, 42 | h6, 43 | header, 44 | hgroup, 45 | hr, 46 | html, 47 | i, 48 | iframe, 49 | img, 50 | ins, 51 | kbd, 52 | label, 53 | legend, 54 | li, 55 | mark, 56 | menu, 57 | meter, 58 | nav, 59 | object, 60 | ol, 61 | output, 62 | p, 63 | pre, 64 | progress, 65 | q, 66 | rp, 67 | rt, 68 | ruby, 69 | s, 70 | samp, 71 | section, 72 | small, 73 | span, 74 | strike, 75 | strong, 76 | sub, 77 | summary, 78 | sup, 79 | table, 80 | tbody, 81 | td, 82 | tfoot, 83 | th, 84 | thead, 85 | time, 86 | tr, 87 | tt, 88 | u, 89 | ul, 90 | var, 91 | video, 92 | xmp { 93 | border: 0; 94 | margin: 0; 95 | padding: 0; 96 | font-size: 100%; 97 | } 98 | 99 | html, 100 | body { 101 | height: 100%; 102 | } 103 | 104 | article, 105 | aside, 106 | details, 107 | figcaption, 108 | figure, 109 | footer, 110 | header, 111 | hgroup, 112 | menu, 113 | nav, 114 | section { 115 | /* 116 | Override the default (display: inline) for 117 | browsers that do not recognize HTML5 tags. 118 | 119 | IE8 (and lower) requires a shiv: 120 | http://ejohn.org/blog/html5-shiv 121 | */ 122 | display: block; 123 | } 124 | 125 | b, 126 | strong { 127 | /* 128 | Makes browsers agree. 129 | IE + Opera = font-weight: bold. 130 | Gecko + WebKit = font-weight: bolder. 131 | */ 132 | font-weight: bold; 133 | } 134 | 135 | img { 136 | color: transparent; 137 | font-size: 0; 138 | vertical-align: middle; 139 | /* 140 | For IE. 141 | http://css-tricks.com/ie-fix-bicubic-scaling-for-images 142 | */ 143 | -ms-interpolation-mode: bicubic; 144 | } 145 | 146 | li { 147 | /* 148 | For IE6 + IE7. 149 | */ 150 | display: list-item; 151 | } 152 | 153 | table { 154 | border-collapse: collapse; 155 | border-spacing: 0; 156 | } 157 | 158 | th, 159 | td, 160 | caption { 161 | font-weight: normal; 162 | vertical-align: top; 163 | text-align: left; 164 | } 165 | 166 | q { 167 | quotes: none; 168 | } 169 | 170 | q:before, 171 | q:after { 172 | content: ''; 173 | content: none; 174 | } 175 | 176 | sub, 177 | sup, 178 | small { 179 | font-size: 75%; 180 | } 181 | 182 | sub, 183 | sup { 184 | line-height: 0; 185 | position: relative; 186 | vertical-align: baseline; 187 | } 188 | 189 | sub { 190 | bottom: -0.25em; 191 | } 192 | 193 | sup { 194 | top: -0.5em; 195 | } 196 | 197 | svg { 198 | /* 199 | For IE9. 200 | */ 201 | overflow: hidden; 202 | } -------------------------------------------------------------------------------- /samples/web/atlas/css/grids/reset_rtl.css: -------------------------------------------------------------------------------- 1 | /* `XHTML, HTML4, HTML5 Reset 2 | ----------------------------------------------------------------------------------------------------*/ 3 | 4 | a, 5 | abbr, 6 | acronym, 7 | address, 8 | applet, 9 | article, 10 | aside, 11 | audio, 12 | b, 13 | big, 14 | blockquote, 15 | body, 16 | canvas, 17 | caption, 18 | center, 19 | cite, 20 | code, 21 | dd, 22 | del, 23 | details, 24 | dfn, 25 | dialog, 26 | div, 27 | dl, 28 | dt, 29 | em, 30 | embed, 31 | fieldset, 32 | figcaption, 33 | figure, 34 | font, 35 | footer, 36 | form, 37 | h1, 38 | h2, 39 | h3, 40 | h4, 41 | h5, 42 | h6, 43 | header, 44 | hgroup, 45 | hr, 46 | html, 47 | i, 48 | iframe, 49 | img, 50 | ins, 51 | kbd, 52 | label, 53 | legend, 54 | li, 55 | mark, 56 | menu, 57 | meter, 58 | nav, 59 | object, 60 | ol, 61 | output, 62 | p, 63 | pre, 64 | progress, 65 | q, 66 | rp, 67 | rt, 68 | ruby, 69 | s, 70 | samp, 71 | section, 72 | small, 73 | span, 74 | strike, 75 | strong, 76 | sub, 77 | summary, 78 | sup, 79 | table, 80 | tbody, 81 | td, 82 | tfoot, 83 | th, 84 | thead, 85 | time, 86 | tr, 87 | tt, 88 | u, 89 | ul, 90 | var, 91 | video, 92 | xmp { 93 | border: 0; 94 | margin: 0; 95 | padding: 0; 96 | font-size: 100%; 97 | } 98 | 99 | html, 100 | body { 101 | height: 100%; 102 | } 103 | 104 | article, 105 | aside, 106 | details, 107 | figcaption, 108 | figure, 109 | footer, 110 | header, 111 | hgroup, 112 | menu, 113 | nav, 114 | section { 115 | /* 116 | Override the default (display: inline) for 117 | browsers that do not recognize HTML5 tags. 118 | 119 | IE8 (and lower) requires a shiv: 120 | http://ejohn.org/blog/html5-shiv 121 | */ 122 | display: block; 123 | } 124 | 125 | b, 126 | strong { 127 | /* 128 | Makes browsers agree. 129 | IE + Opera = font-weight: bold. 130 | Gecko + WebKit = font-weight: bolder. 131 | */ 132 | font-weight: bold; 133 | } 134 | 135 | img { 136 | color: transparent; 137 | font-size: 0; 138 | vertical-align: middle; 139 | /* 140 | For IE. 141 | http://css-tricks.com/ie-fix-bicubic-scaling-for-images 142 | */ 143 | -ms-interpolation-mode: bicubic; 144 | } 145 | 146 | li { 147 | /* 148 | For IE6 + IE7. 149 | */ 150 | display: list-item; 151 | } 152 | 153 | table { 154 | border-collapse: collapse; 155 | border-spacing: 0; 156 | } 157 | 158 | th, 159 | td, 160 | caption { 161 | font-weight: normal; 162 | vertical-align: top; 163 | text-align: right; 164 | } 165 | 166 | q { 167 | quotes: none; 168 | } 169 | 170 | q:before, 171 | q:after { 172 | content: ''; 173 | content: none; 174 | } 175 | 176 | sub, 177 | sup, 178 | small { 179 | font-size: 75%; 180 | } 181 | 182 | sub, 183 | sup { 184 | line-height: 0; 185 | position: relative; 186 | vertical-align: baseline; 187 | } 188 | 189 | sub { 190 | bottom: -0.25em; 191 | } 192 | 193 | sup { 194 | top: -0.5em; 195 | } 196 | 197 | svg { 198 | /* 199 | For IE9. 200 | */ 201 | overflow: hidden; 202 | } -------------------------------------------------------------------------------- /samples/src/auth_demo.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- auth_cb -- Authentication callback examples 3 | -- Copyright (C) 2013, 2020 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | with Util.Properties; 8 | with Util.Log.Loggers; 9 | with Util.Http.Clients.AWS; 10 | 11 | with AWS.Config; 12 | with AWS.Config.Set; 13 | with AWS.Server; 14 | with AWS.Services.Dispatchers.URI; 15 | with AWS.Services.Page_Server; 16 | with AWS.Services.Web_Block.Registry; 17 | with AWS.Net.SSL; 18 | 19 | with Auth_CB; 20 | 21 | procedure Auth_Demo is 22 | 23 | Log : constant Util.Log.Loggers.Logger := Util.Log.Loggers.Create ("Auth_Demo"); 24 | 25 | Dispatcher : AWS.Services.Dispatchers.URI.Handler; 26 | WS : AWS.Server.HTTP; 27 | Config : AWS.Config.Object; 28 | begin 29 | if not AWS.Net.SSL.Is_Supported then 30 | Log.Error ("SSL is not supported by AWS."); 31 | Log.Error ("SSL is required for the OpenID connector to connect to OpenID providers."); 32 | Log.Error ("Please, rebuild AWS with SSL support."); 33 | return; 34 | end if; 35 | 36 | -- Get the authentication provider configuration. We use the Util.Properties and some 37 | -- java like property file. Other configuration implementation are possible. 38 | Auth_CB.Config.Load_Properties ("samples.properties"); 39 | Util.Log.Loggers.Initialize (Util.Properties.Manager (Auth_CB.Config)); 40 | 41 | -- Setup the HTTP client implementation to use AWS. 42 | Util.Http.Clients.AWS.Register; 43 | 44 | -- Setup AWS dispatchers. 45 | AWS.Services.Dispatchers.URI.Register (Dispatcher, "/atlas/auth/auth", 46 | Auth_CB.Get_Authorization'Access, 47 | Prefix => True); 48 | AWS.Services.Dispatchers.URI.Register (Dispatcher, "/verify", 49 | Auth_CB.Verify_Authorization'Access); 50 | AWS.Services.Dispatchers.URI.Register (Dispatcher, "/atlas", 51 | AWS.Services.Page_Server.Callback'Access, 52 | Prefix => True); 53 | AWS.Services.Dispatchers.URI.Register (Dispatcher, "/success", 54 | Auth_CB.User_Info'Access); 55 | AWS.Services.Web_Block.Registry.Register ("success", "samples/web/success.thtml", null); 56 | 57 | -- Configure AWS. 58 | Config := AWS.Config.Get_Current; 59 | AWS.Config.Set.Session (Config, True); 60 | AWS.Config.Set.Session_Name (Config, "AUTH_DEMO"); 61 | AWS.Config.Set.Reuse_Address (Config, True); 62 | AWS.Config.Set.WWW_Root (Config, "samples/web"); 63 | 64 | AWS.Server.Start (WS, Dispatcher => Dispatcher, Config => Config); 65 | 66 | Log.Info ("Connect you browser to: http://localhost:8080/atlas/login.html"); 67 | Log.Info ("Press 'q' key to stop the server."); 68 | 69 | AWS.Server.Wait (AWS.Server.Q_Key_Pressed); 70 | 71 | Log.Info ("Shutting down server..."); 72 | AWS.Server.Shutdown (WS); 73 | end Auth_Demo; 74 | -------------------------------------------------------------------------------- /regtests/src/security-random-tests.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-random-tests - Tests for random package 3 | -- Copyright (C) 2017, 2018, 2022, 2023 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Ada.Strings.Unbounded; 9 | 10 | with Util.Test_Caller; 11 | package body Security.Random.Tests is 12 | 13 | package Caller is new Util.Test_Caller (Test, "Security.Random"); 14 | 15 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite) is 16 | begin 17 | Caller.Add_Test (Suite, "Test Security.Random.Generate", 18 | Test_Generate'Access); 19 | Caller.Add_Test (Suite, "Test Security.Random.Generate_String", 20 | Test_Generate_String'Access); 21 | end Add_Tests; 22 | 23 | -- ------------------------------ 24 | -- Test Yadis discovery using static files 25 | -- ------------------------------ 26 | procedure Test_Generate (T : in out Test) is 27 | use Ada.Strings.Unbounded; 28 | 29 | G : Generator; 30 | Max : constant Ada.Streams.Stream_Element_Offset := 10; 31 | begin 32 | for I in 1 .. Max loop 33 | declare 34 | use type Ada.Streams.Stream_Element; 35 | S : Ada.Streams.Stream_Element_Array (1 .. I) 36 | := (others => 0); 37 | Rand : Ada.Strings.Unbounded.Unbounded_String; 38 | begin 39 | -- Try 5 times to fill the array with random patterns and make sure 40 | -- we don't get any 0. 41 | for Retry in 1 .. 5 loop 42 | G.Generate (S); 43 | exit when (for all R of S => R /= 0); 44 | end loop; 45 | T.Assert ((for all R of S => R /= 0), "Generator failed to initialize all bytes"); 46 | 47 | G.Generate (Positive (I), Rand); 48 | T.Assert (Length (Rand) > 0, "Generator failed to produce a base64url sequence"); 49 | end; 50 | end loop; 51 | end Test_Generate; 52 | 53 | procedure Test_Generate_String (T : in out Test) is 54 | G : Generator; 55 | begin 56 | declare 57 | S1 : constant String := G.Generate (Bits => 256); 58 | S2 : constant String := G.Generate (Bits => 256); 59 | begin 60 | Util.Tests.Assert_Equals (T, 43, Natural (S1'Length), 61 | "Generated string 1 is too small"); 62 | Util.Tests.Assert_Equals (T, 43, Natural (S2'Length), 63 | "Generated string 2 is too small"); 64 | T.Assert (S1 /= S2, "Generated string are equal"); 65 | end; 66 | for I in 1 .. 100 loop 67 | declare 68 | S1 : String (1 .. I) := (others => ' '); 69 | S2 : String (1 .. I) := (others => ' '); 70 | begin 71 | G.Generate (S1); 72 | G.Generate (S2); 73 | T.Assert (S1 /= S2, "Generated string are equal"); 74 | end; 75 | end loop; 76 | end Test_Generate_String; 77 | 78 | end Security.Random.Tests; 79 | -------------------------------------------------------------------------------- /src/security-auth-oauth.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth -- OAuth based authentication 3 | -- Copyright (C) 2013, 2020 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Security.OAuth.Clients; 9 | private package Security.Auth.OAuth is 10 | 11 | -- ------------------------------ 12 | -- OAuth Manager 13 | -- ------------------------------ 14 | -- The Manager provides the core operations for the OAuth authorization process. 15 | type Manager is abstract new Security.Auth.Manager with private; 16 | 17 | -- Initialize the authentication realm. 18 | overriding 19 | procedure Initialize (Realm : in out Manager; 20 | Params : in Parameters'Class; 21 | Provider : in String := PROVIDER_OPENID); 22 | 23 | -- Discover the OpenID provider that must be used to authenticate the user. 24 | -- The Name can be an URL or an alias that identifies the provider. 25 | -- A cached OpenID provider can be returned. 26 | -- Read the XRDS document from the URI and initialize the OpenID provider end point. 27 | -- (See OpenID Section 7.3 Discovery) 28 | overriding 29 | procedure Discover (Realm : in out Manager; 30 | Name : in String; 31 | Result : out End_Point); 32 | 33 | -- Associate the application (relying party) with the OpenID provider. 34 | -- The association can be cached. 35 | -- (See OpenID Section 8 Establishing Associations) 36 | overriding 37 | procedure Associate (Realm : in out Manager; 38 | OP : in End_Point; 39 | Result : out Association); 40 | 41 | -- Get the authentication URL to which the user must be redirected for authentication 42 | -- by the authentication server. 43 | overriding 44 | function Get_Authentication_URL (Realm : in Manager; 45 | OP : in End_Point; 46 | Assoc : in Association) return String; 47 | 48 | -- Verify the authentication result 49 | overriding 50 | procedure Verify (Realm : in out Manager; 51 | Assoc : in Association; 52 | Request : in Parameters'Class; 53 | Result : out Authentication); 54 | 55 | -- Verify the OAuth access token and retrieve information about the user. 56 | procedure Verify_Access_Token (Realm : in Manager; 57 | Assoc : in Association; 58 | Request : in Parameters'Class; 59 | Token : in Security.OAuth.Clients.Access_Token_Access; 60 | Result : in out Authentication) is abstract; 61 | 62 | private 63 | 64 | type Manager is abstract new Security.Auth.Manager with record 65 | Return_To : Unbounded_String; 66 | Realm : Unbounded_String; 67 | Scope : Unbounded_String; 68 | Issuer : Unbounded_String; 69 | App : Security.OAuth.Clients.Application; 70 | end record; 71 | 72 | end Security.Auth.OAuth; 73 | -------------------------------------------------------------------------------- /docs/Security_OAuth_Servers.md: -------------------------------------------------------------------------------- 1 | ## OAuth Server 2 | OAuth server side is provided by the Security.OAuth.Servers package. 3 | This package allows to implement the authorization framework described in RFC 6749 4 | "The OAuth 2.0 Authorization Framework". 5 | 6 | The authorization method produces a Grant_Type object that contains the result 7 | of the grant (successful or denied). It is the responsibility of the caller to format 8 | the result in JSON/XML and return it to the client. 9 | 10 | Three important operations are defined for the OAuth 2.0 framework. They will be used 11 | in the following order: 12 | 13 | Authorize is used to obtain an authorization request. This operation is 14 | optional in the OAuth 2.0 framework since some authorization method directly return 15 | the access token. This operation is used by the "Authorization Code Grant" and the 16 | "Implicit Grant". 17 | 18 | Token is used to get the access token and optional refresh token. Each time it 19 | is called, a new token is generated. 20 | 21 | Authenticate is used for the API request to verify the access token 22 | and authenticate the API call. This operation can be called several times with the same 23 | token until the token is revoked or it has expired. 24 | 25 | Several grant types are supported. 26 | 27 | ### Application Manager 28 | The application manager maintains the repository of applications which are known by 29 | the server and which can request authorization. Each application is identified by 30 | a client identifier (represented by the client_id request parameter). 31 | The application defines the authorization methods which are allowed as well as 32 | the parameters to control and drive the authorization. This includes the redirection 33 | URI, the application secret, the expiration delay for the access token. 34 | 35 | The application manager is implemented by the application server and it must 36 | implement the Application_Manager interface with the Find_Application 37 | method. The Find_Application is one of the first call made during the 38 | authenticate and token generation phases. 39 | 40 | ### Resource Owner Password Credentials Grant 41 | The password grant is one of the easiest grant method to understand but it is also one 42 | of the less secure. In this grant method, the username and user password are passed in 43 | the request parameter together with the application client identifier. The realm verifies 44 | the username and password and when they are correct it generates the access token with 45 | an optional refresh token. The realm also returns in the grant the user principal that 46 | identifies the user. 47 | 48 | ```Ada 49 | Realm : Security.OAuth.Servers.Auth_Manager; 50 | Grant : Security.OAuth.Servers.Grant_Type; 51 | Realm.Token (Params, Grant); 52 | 53 | ``` 54 | 55 | ### Accessing Protected Resources 56 | When accessing a protected resource, the API implementation will use the 57 | Authenticate operation to verify the access token and get a security principal. 58 | The security principal will identify the resource owner as well as the application 59 | that is doing the call. 60 | 61 | ```Ada 62 | Realm : Security.OAuth.Servers.Auth_Manager; 63 | Grant : Security.OAuth.Servers.Grant_Type; 64 | Token : String := ...; 65 | Realm.Authenticate (Token, Grant); 66 | 67 | ``` 68 | 69 | When a security principal is returned, the access token was validated and the 70 | request is granted for the application. 71 | 72 | 73 | -------------------------------------------------------------------------------- /samples.properties: -------------------------------------------------------------------------------- 1 | openid.realm=http://localhost:8080/verify 2 | openid.callback_url=http://localhost:8080/verify 3 | openid.success_url=http://localhost:8080/success 4 | auth.url.orange=https://openid.orange.fr 5 | auth.url.yahoo=https://api.login.yahoo.com/oauth2/request_auth 6 | auth.url.facebook=https://www.facebook.com/dialog/oauth 7 | auth.url.google-plus=https://accounts.google.com/o/oauth2/auth 8 | auth.url.gitlab=https://gitlab.com/oauth/authorize 9 | auth.url.github=https://github.com/login/oauth/authorize 10 | auth.url.twitter=https://api.twitter.com/oauth/authenticate 11 | auth.provider.orange=openid 12 | auth.provider.google=openid 13 | auth.provider.yahoo=yahoo 14 | auth.provider.twitter=yahoo 15 | auth.provider.gitlab=yahoo 16 | auth.provider.github=github 17 | auth.provider.facebook=facebook 18 | auth.provider.google-plus=google-plus 19 | 20 | # Get an account on Facebook 21 | facebook.client_id=PUT-HERE-YOUR-FACEBOOK-CLIENT-ID 22 | facebook.secret=PUT-HERE-YOUR-FACEBOOK-API-SECRET 23 | facebook.callback=https://demo.vacs.fr/oauth/localhost:8080/verify 24 | facebook.callback_url=https://demo.vacs.fr/oauth/localhost:8080/verify 25 | facebook.request_url=https://graph.facebook.com/oauth/access_token 26 | facebook.scope=read_stream,email 27 | 28 | # Get an account on Google+ 29 | google-plus.issuer=accounts.google.com 30 | google-plus.client_id=PUT-HERE-YOUR-GOOGLE-CLIENT-ID 31 | google-plus.secret=PUT-HERE-YOUR-GOOGLE-API-SECRET 32 | google-plus.callback=https://demo.vacs.fr/oauth/localhost:8080/verify 33 | google-plus.callback_url=https://demo.vacs.fr/oauth/localhost:8080/verify 34 | google-plus.request_url=https://accounts.google.com/o/oauth2/token 35 | google-plus.scope=openid profile email 36 | 37 | # Yahoo! OpenId connect 38 | yahoo.issuer=https://api.login.yahoo.com 39 | yahoo.request_url=https://api.login.yahoo.com/oauth2/get_token 40 | yahoo.client_id=PUT-HERE-YOUR-YAHOO-CLIENT-ID 41 | yahoo.secret=PUT-HERE-YOUR-YAHOO-API-SECRET 42 | yahoo.app_id=PUT-HERE-YOUR-YAHOO-APP-ID 43 | yahoo.callback_url=https://demo.vacs.fr/oauth/localhost:8080/verify 44 | yahoo.scope=openid,profile,email 45 | 46 | # Yahoo! OpenId connect 47 | twitter.issuer=https://api.login.yahoo.com 48 | twitter.request_url=https://api.login.yahoo.com/oauth2/get_token 49 | twitter.client_id= 50 | twitter.secret= 51 | twitter.callback_url=https://demo.vacs.fr/oauth/localhost:8080/verify 52 | twitter.scope=openid,profile,email 53 | 54 | gitlab.issuer=https://gitlab.com 55 | gitlab.request_url=https://gitlab.com/oauth/token 56 | gitlab.client_id=PUT-HERE-YOUR-GITLAB-CLIENT-ID 57 | gitlab.secret=PUT-HERE-YOUR-GITLAB-SECRET 58 | gitlab.callback_url=https://demo.vacs.fr/oauth/localhost:8080/verify 59 | gitlab.scope=openid email 60 | 61 | github.issuer=https://github.com 62 | github.request_url=https://github.com/login/oauth/access_token 63 | github.client_id=PUT-HERE-YOUR-GITHUB-CLIENT-ID 64 | github.secret=PUT-HERE-YOUR-GITHUB-SECRET 65 | github.callback_url=https://demo.vacs.fr/oauth/localhost:8080/verify 66 | github.scope=openid 67 | 68 | # Configuration for log4j 69 | log4j.rootCategory=DEBUG,console,result 70 | log4j.appender.console=Console 71 | log4j.appender.console.level=DEBUG 72 | log4j.appender.console.layout=level-message 73 | log4j.appender.result=File 74 | log4j.appender.result.File=samples.log 75 | 76 | # Logger configuration 77 | log4j.logger.log=WARN 78 | log4j.logger.Util.Properties=DEBUG 79 | log4j.logger.Util.Log=DEBUG 80 | log4j.logger.Util=DEBUG 81 | log4j.logger.Util.Serialize.Mappers=WARN 82 | log4j.logger.Util.Serialize.IO=INFO 83 | log4j.logger.Security=DEBUG 84 | -------------------------------------------------------------------------------- /src/security-permissions.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-permissions -- Definition of permissions 3 | -- Copyright (C) 2010, 2011, 2012, 2016, 2017 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | private with Interfaces; 8 | 9 | -- == Permission == 10 | -- The Security.Permissions package defines the different permissions that can be 11 | -- checked by the access control manager. An application should declare each permission 12 | -- by instantiating the Definition package: 13 | -- 14 | -- package Perm_Create_Workspace is new Security.Permissions.Definition ("create-workspace"); 15 | -- 16 | -- This declares a permission that can be represented by "create-workspace" in 17 | -- configuration files. In Ada, the permission is used as follows: 18 | -- 19 | -- Perm_Create_Workspace.Permission 20 | -- 21 | package Security.Permissions is 22 | 23 | Invalid_Name : exception; 24 | 25 | -- Max number of permissions supported by the implementation. 26 | MAX_PERMISSION : constant Natural := 255; 27 | 28 | type Permission_Index is new Natural range 0 .. MAX_PERMISSION; 29 | 30 | type Permission_Index_Array is array (Positive range <>) of Permission_Index; 31 | 32 | NONE : constant Permission_Index := Permission_Index'First; 33 | 34 | -- Get the permission index associated with the name. 35 | function Get_Permission_Index (Name : in String) return Permission_Index; 36 | 37 | -- Get the list of permissions whose name is given in the string with separated comma. 38 | function Get_Permission_Array (List : in String) return Permission_Index_Array; 39 | 40 | -- Get the permission name given the index. 41 | function Get_Name (Index : in Permission_Index) return String; 42 | 43 | -- The permission root class. 44 | -- Each permission is represented by a Permission_Index number to provide a fast 45 | -- and efficient permission check. 46 | type Permission (Id : Permission_Index) is tagged limited null record; 47 | 48 | generic 49 | Name : String; 50 | package Definition is 51 | function Permission return Permission_Index; 52 | pragma Inline_Always (Permission); 53 | end Definition; 54 | 55 | -- Add the permission name and allocate a unique permission index. 56 | procedure Add_Permission (Name : in String; 57 | Index : out Permission_Index); 58 | 59 | type Permission_Index_Set is private; 60 | 61 | -- Check if the permission index set contains the given permission index. 62 | function Has_Permission (Set : in Permission_Index_Set; 63 | Index : in Permission_Index) return Boolean; 64 | 65 | -- Add the permission index to the set. 66 | procedure Add_Permission (Set : in out Permission_Index_Set; 67 | Index : in Permission_Index); 68 | 69 | -- Get the last permission index registered in the global permission map. 70 | function Get_Last_Permission_Index return Permission_Index; 71 | 72 | -- The empty set of permission indexes. 73 | EMPTY_SET : constant Permission_Index_Set; 74 | 75 | private 76 | 77 | INDEX_SET_SIZE : constant Natural := (MAX_PERMISSION + 7) / 8; 78 | 79 | type Permission_Index_Set is array (0 .. INDEX_SET_SIZE - 1) of Interfaces.Unsigned_8; 80 | 81 | EMPTY_SET : constant Permission_Index_Set := (others => 0); 82 | 83 | end Security.Permissions; 84 | -------------------------------------------------------------------------------- /src/security-auth-oauth-googleplus.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth-googleplus -- Google+ OAuth based authentication 3 | -- Copyright (C) 2013, 2020 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Log.Loggers; 9 | with Security.OAuth.JWT; 10 | package body Security.Auth.OAuth.Googleplus is 11 | 12 | use Util.Log; 13 | 14 | Log : constant Loggers.Logger := Util.Log.Loggers.Create ("Security.Auth.OAuth.Googleplus"); 15 | 16 | -- ------------------------------ 17 | -- Verify the OAuth access token and retrieve information about the user. 18 | -- ------------------------------ 19 | overriding 20 | procedure Verify_Access_Token (Realm : in Manager; 21 | Assoc : in Association; 22 | Request : in Parameters'Class; 23 | Token : in Security.OAuth.Clients.Access_Token_Access; 24 | Result : in out Authentication) is 25 | pragma Unreferenced (Request); 26 | begin 27 | -- The token returned by Google+ must contain an id_token. 28 | if not (Token.all in Security.OAuth.Clients.OpenID_Token'Class) then 29 | Log.Warn ("Invalid token instance created: missing the id_token"); 30 | Set_Result (Result, INVALID_SIGNATURE, "invalid access token returned"); 31 | return; 32 | end if; 33 | 34 | -- The id_token is a JWT token that must be decoded and verified. 35 | -- See https://developers.google.com/accounts/docs/OAuth2Login#validatingtoken 36 | -- It contains information to identify the user. 37 | declare 38 | T : constant Security.OAuth.Clients.OpenID_Token_Access := 39 | Security.OAuth.Clients.OpenID_Token'Class (Token.all)'Access; 40 | Info : constant Security.OAuth.JWT.Token := Security.OAuth.JWT.Decode (T.Get_Id_Token); 41 | begin 42 | -- Verify that the JWT token concerns our application. 43 | if Security.OAuth.JWT.Get_Audience (Info) /= Realm.App.Get_Application_Identifier then 44 | Set_Result (Result, INVALID_SIGNATURE, 45 | "the access token was granted for another application"); 46 | return; 47 | 48 | -- Verify that the issuer is Google+ 49 | elsif Security.OAuth.JWT.Get_Issuer (Info) /= Realm.Issuer then 50 | Set_Result (Result, INVALID_SIGNATURE, 51 | "the access token was not generated by the expected authority"); 52 | return; 53 | 54 | -- Verify that the nonce we received matches our nonce. 55 | elsif Security.OAuth.JWT.Get_Claim (Info, "nonce") /= Assoc.Nonce then 56 | Set_Result (Result, INVALID_SIGNATURE, 57 | "the access token was not generated with the expected nonce"); 58 | return; 59 | 60 | end if; 61 | Result.Identity := To_Unbounded_String ("https://accounts.google.com/"); 62 | Append (Result.Identity, Security.OAuth.JWT.Get_Subject (Info)); 63 | Result.Claimed_Id := Result.Identity; 64 | 65 | -- The email is optional and depends on the scope. 66 | Result.Email := To_Unbounded_String (Security.OAuth.JWT.Get_Claim (Info, "email", "")); 67 | Set_Result (Result, AUTHENTICATED, "authenticated"); 68 | end; 69 | end Verify_Access_Token; 70 | 71 | end Security.Auth.OAuth.Googleplus; 72 | -------------------------------------------------------------------------------- /src/security-oauth-jwt.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-oauth-jwt -- OAuth Java Web Token 3 | -- Copyright (C) 2013 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Ada.Calendar; 9 | with Util.Properties; 10 | 11 | -- === JSON Web Token === 12 | -- JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred 13 | -- between two parties. A JWT token is returned by an authorization server. It contains 14 | -- useful information that allows to verify the authentication and identify the user. 15 | -- 16 | -- The Security.OAuth.JWT package implements the decoding part of JWT defined in: 17 | -- JSON Web Token (JWT), http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-07 18 | -- 19 | -- A list of pre-defined ID tokens are returned in the JWT token claims and used for 20 | -- the OpenID Connect. This is specified in 21 | -- OpenID Connect Basic Client Profile 1.0 - draft 26, 22 | -- http://openid.net/specs/openid-connect-basic-1_0.html 23 | -- 24 | package Security.OAuth.JWT is 25 | 26 | -- Exception raised if the encoded token is invalid or cannot be decoded. 27 | Invalid_Token : exception; 28 | 29 | type Token is private; 30 | 31 | -- Get the issuer claim from the token (the "iss" claim). 32 | function Get_Issuer (From : in Token) return String; 33 | 34 | -- Get the subject claim from the token (the "sub" claim). 35 | function Get_Subject (From : in Token) return String; 36 | 37 | -- Get the audience claim from the token (the "aud" claim). 38 | function Get_Audience (From : in Token) return String; 39 | 40 | -- Get the expiration claim from the token (the "exp" claim). 41 | function Get_Expiration (From : in Token) return Ada.Calendar.Time; 42 | 43 | -- Get the not before claim from the token (the "nbf" claim). 44 | function Get_Not_Before (From : in Token) return Ada.Calendar.Time; 45 | 46 | -- Get the issued at claim from the token (the "iat" claim). 47 | -- This is the time when the JWT was issued. 48 | function Get_Issued_At (From : in Token) return Ada.Calendar.Time; 49 | 50 | -- Get the authentication time claim from the token (the "auth_time" claim). 51 | function Get_Authentication_Time (From : in Token) return Ada.Calendar.Time; 52 | 53 | -- Get the JWT ID claim from the token (the "jti" claim). 54 | function Get_JWT_ID (From : in Token) return String; 55 | 56 | -- Get the authorized clients claim from the token (the "azp" claim). 57 | function Get_Authorized_Presenters (From : in Token) return String; 58 | 59 | -- Get the claim with the given name from the token. 60 | function Get_Claim (From : in Token; 61 | Name : in String; 62 | Default : in String := "") return String; 63 | 64 | -- Decode a string representing an encoded JWT token according to the JWT specification: 65 | -- 66 | -- Section 7. Rules for Creating and Validating a JWT 67 | -- 68 | -- The JWT token is composed of 3 parts encoded in Base64url and separated by '.' . 69 | -- The first part represents the header, the second part the claims and the last part 70 | -- the signature. The Decode operation splits the parts, decodes them, 71 | -- parses the JSON content represented by the header and the claims. 72 | -- The Decode operation does not verify the signature (yet!). 73 | -- 74 | -- Return the decoded token or raise an exception. 75 | function Decode (Content : in String) return Token; 76 | 77 | private 78 | 79 | type Claims is new Util.Properties.Manager with null record; 80 | 81 | type Token is record 82 | Header : Util.Properties.Manager; 83 | Claims : Util.Properties.Manager; 84 | end record; 85 | 86 | end Security.OAuth.JWT; 87 | -------------------------------------------------------------------------------- /src/security-auth-oauth-yahoo.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth-yahoo -- Yahoo! OAuth based authentication 3 | -- Copyright (C) 2013, 2014, 2020 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Log.Loggers; 9 | with Security.OAuth.JWT; 10 | package body Security.Auth.OAuth.Yahoo is 11 | 12 | use Util.Log; 13 | use Security.OAuth; 14 | 15 | Log : constant Loggers.Logger := Util.Log.Loggers.Create ("Security.Auth.OAuth.Yahoo"); 16 | 17 | -- ------------------------------ 18 | -- Verify the OAuth access token and retrieve information about the user. 19 | -- ------------------------------ 20 | overriding 21 | procedure Verify_Access_Token (Realm : in Manager; 22 | Assoc : in Association; 23 | Request : in Parameters'Class; 24 | Token : in Security.OAuth.Clients.Access_Token_Access; 25 | Result : in out Authentication) is 26 | pragma Unreferenced (Request); 27 | begin 28 | -- The token returned by Yahoo! must contain an id_token. 29 | if not (Token.all in Security.OAuth.Clients.OpenID_Token'Class) then 30 | Log.Warn ("Invalid token instance created: missing the id_token"); 31 | Set_Result (Result, INVALID_SIGNATURE, "invalid access token returned"); 32 | return; 33 | end if; 34 | 35 | -- The id_token is a JWT token that must be decoded and verified. 36 | -- See https://developer.yahoo.com/oauth2/guide/openid_connect/decode_id_token.html 37 | -- It contains information to identify the user. 38 | declare 39 | T : constant Security.OAuth.Clients.OpenID_Token_Access := 40 | Security.OAuth.Clients.OpenID_Token'Class (Token.all)'Access; 41 | Info : constant Security.OAuth.JWT.Token := Security.OAuth.JWT.Decode (T.Get_Id_Token); 42 | begin 43 | -- Verify that the JWT token concerns our application. 44 | if Security.OAuth.JWT.Get_Audience (Info) /= Realm.App.Get_Application_Identifier then 45 | Set_Result (Result, INVALID_SIGNATURE, 46 | "the access token was granted for another application"); 47 | return; 48 | 49 | -- Verify that the issuer is Yahoo! 50 | elsif Security.OAuth.JWT.Get_Issuer (Info) /= Realm.Issuer then 51 | Set_Result (Result, INVALID_SIGNATURE, 52 | "the access token was not generated by the expected authority"); 53 | return; 54 | 55 | -- Verify that the nonce we received matches our nonce. 56 | elsif Security.OAuth.JWT.Get_Claim (Info, "nonce") /= Assoc.Nonce then 57 | Set_Result (Result, INVALID_SIGNATURE, 58 | "the access token was not generated with the expected nonce"); 59 | return; 60 | 61 | end if; 62 | Result.Identity := Realm.Issuer; 63 | Append (Result.Identity, "/"); 64 | Append (Result.Identity, Security.OAuth.JWT.Get_Subject (Info)); 65 | Result.Claimed_Id := Result.Identity; 66 | Result.First_Name := To_Unbounded_String (JWT.Get_Claim (Info, "name", "")); 67 | Result.Last_Name := To_Unbounded_String (JWT.Get_Claim (Info, "family_name", "")); 68 | Result.Gender := To_Unbounded_String (JWT.Get_Claim (Info, "gender", "")); 69 | Result.Nickname := To_Unbounded_String (JWT.Get_Claim (Info, "nickname", "")); 70 | Result.Full_Name := To_Unbounded_String (JWT.Get_Claim (Info, "name", "")); 71 | 72 | -- The email is optional and depends on the scope. 73 | Result.Email := To_Unbounded_String (JWT.Get_Claim (Info, "email", "")); 74 | Set_Result (Result, AUTHENTICATED, "authenticated"); 75 | end; 76 | end Verify_Access_Token; 77 | 78 | end Security.Auth.OAuth.Yahoo; 79 | -------------------------------------------------------------------------------- /src/security-auth-openid.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-openid -- OpenID 2.0 Support 3 | -- Copyright (C) 2009, 2010, 2011, 2012, 2018 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | -- == OpenID Configuration == 9 | -- The Open ID provider needs the following configuration parameters: 10 | -- 11 | -- openid.realm The OpenID realm parameter passed in the authentication URL. 12 | -- openid.callback_url The OpenID return_to parameter. 13 | -- 14 | private package Security.Auth.OpenID is 15 | 16 | -- ------------------------------ 17 | -- OpenID Manager 18 | -- ------------------------------ 19 | -- The Manager provides the core operations for the OpenID process. 20 | type Manager is new Security.Auth.Manager with private; 21 | 22 | -- Initialize the OpenID authentication realm. Get the openid.realm 23 | -- and openid.callback_url parameters to configure the realm. 24 | overriding 25 | procedure Initialize (Realm : in out Manager; 26 | Params : in Parameters'Class; 27 | Name : in String := PROVIDER_OPENID); 28 | 29 | -- Discover the OpenID provider that must be used to authenticate the user. 30 | -- The Name can be an URL or an alias that identifies the provider. 31 | -- A cached OpenID provider can be returned. 32 | -- Read the XRDS document from the URI and initialize the OpenID provider end point. 33 | -- (See OpenID Section 7.3 Discovery) 34 | overriding 35 | procedure Discover (Realm : in out Manager; 36 | Name : in String; 37 | Result : out End_Point); 38 | 39 | -- Associate the application (relying party) with the OpenID provider. 40 | -- The association can be cached. 41 | -- (See OpenID Section 8 Establishing Associations) 42 | overriding 43 | procedure Associate (Realm : in out Manager; 44 | OP : in End_Point; 45 | Result : out Association); 46 | 47 | -- Get the authentication URL to which the user must be redirected for authentication 48 | -- by the authentication server. 49 | overriding 50 | function Get_Authentication_URL (Realm : in Manager; 51 | OP : in End_Point; 52 | Assoc : in Association) return String; 53 | 54 | -- Verify the authentication result 55 | overriding 56 | procedure Verify (Realm : in out Manager; 57 | Assoc : in Association; 58 | Request : in Parameters'Class; 59 | Result : out Authentication); 60 | 61 | -- Verify the authentication result 62 | procedure Verify_Discovered (Realm : in out Manager; 63 | Assoc : in Association; 64 | Request : in Parameters'Class; 65 | Result : out Authentication); 66 | 67 | -- Verify the signature part of the result 68 | procedure Verify_Signature (Realm : in Manager; 69 | Assoc : in Association; 70 | Request : in Parameters'Class; 71 | Result : in out Authentication); 72 | 73 | -- Extract from the XRDS content the OpenID provider URI. 74 | -- The default implementation is very basic as it returns the first 75 | -- available in the stream without validating the XRDS document. 76 | -- Raises the Invalid_End_Point exception if the URI cannot be found. 77 | procedure Extract_XRDS (Realm : in out Manager; 78 | Content : in String; 79 | Result : out End_Point); 80 | 81 | private 82 | 83 | type Manager is new Security.Auth.Manager with record 84 | Return_To : Unbounded_String; 85 | Realm : Unbounded_String; 86 | end record; 87 | 88 | end Security.Auth.OpenID; 89 | -------------------------------------------------------------------------------- /src/security.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security -- Security 3 | -- Copyright (C) 2010, 2011, 2012, 2015, 2017, 2018, 2019 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | -- = Security = 9 | -- The `Security` package provides a security framework that allows 10 | -- an application to use OpenID or OAuth security frameworks. This security 11 | -- framework was first developed within the Ada Server Faces project. 12 | -- It was moved to a separate project so that it can easily be used with AWS. 13 | -- This package defines abstractions that are close or similar to Java 14 | -- security package. 15 | -- 16 | -- The security framework uses the following abstractions: 17 | -- 18 | -- * **Policy and policy manager**: 19 | -- The `Policy` defines and implements the set of security rules that specify how to 20 | -- protect the system or resources. The `Policy_Manager` maintains the security policies. 21 | -- 22 | -- * **Principal**: 23 | -- The `Principal` is the entity that can be authenticated. A principal is obtained 24 | -- after successful authentication of a user or of a system through an authorization process. 25 | -- The OpenID or OAuth authentication processes generate such security principal. 26 | -- 27 | -- * **Permission**: 28 | -- The `Permission` represents an access to a system or application resource. 29 | -- A permission is checked by using the security policy manager. The policy manager uses a 30 | -- security controller to enforce the permission. 31 | -- 32 | -- The `Security_Context` holds the contextual information that the security controller 33 | -- can use to verify the permission. The security context is associated with a principal and 34 | -- a set of policy context. 35 | -- 36 | -- == Overview == 37 | -- An application will create a security policy manager and register one or several security 38 | -- policies (yellow). The framework defines a simple role based security policy and an URL 39 | -- security policy intended to provide security in web applications. The security policy manager 40 | -- reads some security policy configuration file which allows the security policies to configure 41 | -- and create the security controllers. These controllers will enforce the security according 42 | -- to the application security rules. All these components are built only once when 43 | -- an application starts. 44 | -- 45 | -- A user is authenticated through an authentication system which creates a `Principal` 46 | -- instance that identifies the user (green). The security framework provides two authentication 47 | -- systems: OpenID and OAuth 2.0 OpenID Connect. 48 | -- 49 | -- [images/ModelOverview.png] 50 | -- 51 | -- When a permission must be enforced, a security context is created and linked to the 52 | -- `Principal` instance (blue). Additional security policy context can be added depending 53 | -- on the application context. To check the permission, the security policy manager is called 54 | -- and it will ask a security controller to verify the permission. 55 | -- 56 | -- The framework allows an application to plug its own security policy, its own policy context, 57 | -- its own principal and authentication mechanism. 58 | -- 59 | -- @include security-permissions.ads 60 | -- 61 | -- == Principal == 62 | -- A principal is created by using either the [Security_Auth OpenID], 63 | -- the [Security_OAuth OAuth] or another authentication mechanism. The authentication produces 64 | -- an object that must implement the `Principal` interface. For example: 65 | -- 66 | -- P : Security.Auth.Principal_Access := Security.Auth.Create_Principal (Auth); 67 | -- 68 | -- or 69 | -- 70 | -- P : Security.OAuth.Clients.Access_Token_Access := Security.OAuth.Clients.Create_Access_Token 71 | -- 72 | -- The principal is then stored in a security context. 73 | -- 74 | -- @include security-contexts.ads 75 | -- 76 | package Security is 77 | 78 | pragma Preelaborate; 79 | 80 | -- ------------------------------ 81 | -- Principal 82 | -- ------------------------------ 83 | type Principal is limited interface; 84 | type Principal_Access is access all Principal'Class; 85 | 86 | -- Get the principal name. 87 | function Get_Name (From : in Principal) return String is abstract; 88 | 89 | end Security; 90 | -------------------------------------------------------------------------------- /samples/web/atlas/css/users.css: -------------------------------------------------------------------------------- 1 | /** 2 | * users.css - Style for users module 3 | * Copyright (C) 2011 Stephane Carrez 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | */ 6 | div.awa-side1, div.awa-side2 { 7 | float: left; 8 | } 9 | div.awa-side1 { 10 | padding-right: 1%; 11 | } 12 | div.awa-side2 { 13 | padding-left: 1%; 14 | width: 48%; 15 | } 16 | li.message { 17 | height: 30px; 18 | overflow: hidden; 19 | } 20 | 21 | div.awa-login h2 { 22 | float: left; 23 | width: 96%; 24 | background: white; 25 | padding: 2%; 26 | -moz-border-radius-topleft: 6px; 27 | -moz-border-radius-topright: 6px; 28 | -webkit-border-top-right-radius: 6px; 29 | -webkit-border-top-left-radius: 6px; 30 | } 31 | div.awa-login { 32 | border: 2px solid #A6C9E2; 33 | background: none repeat scroll 0 0 #F9FDFF; 34 | display: block; 35 | float: left; 36 | width: 98%; 37 | margin: 5px; 38 | -moz-border-radius: 6px; 39 | -webkit-border-radius: 6px 6px; 40 | } 41 | div.awa-lost-password { 42 | width: 50%; 43 | } 44 | div.awa-login p { 45 | width: 96%; 46 | padding-left: 2%; 47 | padding-right: 2%; 48 | padding-top: 5px; 49 | display: block; 50 | float: left; 51 | } 52 | .awa-login ul, #awa-message-list { 53 | list-style-type: none; 54 | margin: 1em 0; 55 | float: left; 56 | width: 96%; 57 | padding-left: 2%; 58 | padding-right: 2%; 59 | } 60 | 61 | .awa-login #login-button, .awa-login #lost-password-button, .awa-login #register-button { 62 | margin-left: 10%; 63 | margin-top: 15px; 64 | width: 200px; 65 | } 66 | .awa-login #lost-password { 67 | clear: both; 68 | display: block; 69 | margin-top: 10px; 70 | float: right; 71 | } 72 | .awa-login #signup-link { 73 | clear: both; 74 | display: block; 75 | float: right; 76 | } 77 | 78 | ul.openid-login li { 79 | float: left; 80 | padding-left: 5px; 81 | } 82 | div.openid-login { 83 | float: left; 84 | border: 1px solid #ccc; 85 | margin-left: 10px; 86 | margin-right: 10px; 87 | margin: 5px; 88 | } 89 | 90 | ul.openid-login a { 91 | display: block; 92 | height: 35px; 93 | text-align: center; 94 | line-height: 35px; 95 | border: 1px solid #ccc; 96 | } 97 | 98 | a.openid-orange em, 99 | a.openid-yahoo em, 100 | a.openid-facebook em, 101 | a.openid-google-plus em, 102 | a.openid-gitlab em, 103 | a.openid-github em, 104 | a.openid-google em { 105 | display: block; 106 | float: left; 107 | background: url(../images/open-id-logos-1.png); 108 | width: 119px; 109 | height: 40px; 110 | overflow: hidden; 111 | } 112 | a.openid-yahoo em { 113 | background-position: 0 -39px; 114 | height: 25px; 115 | } 116 | div.openid-login a.openid-orange { 117 | padding-left: 0; 118 | } 119 | a.openid-orange em { 120 | background-position: 0 -242px; 121 | height: 35px; 122 | } 123 | a.openid-facebook em { 124 | margin-top: 3px; 125 | margin-left: 3px; 126 | background-position: 0 -280px; 127 | height: 33px; 128 | width: 40px; 129 | } 130 | a.openid-google-plus em { 131 | margin-left: 3px; 132 | margin-right: 10px; 133 | background-position: 0 -312px; 134 | height: 33px; 135 | width: 37px; 136 | border-right: 1px solid #bb3f30; 137 | } 138 | a.openid-github em { 139 | margin-left: 3px; 140 | margin-right: 10px; 141 | background-position: 0 -68px; 142 | height: 33px; 143 | width: 120px; 144 | } 145 | a.openid-gitlab em { 146 | margin-left: 3px; 147 | margin-right: 10px; 148 | background-position: 0 -348px; 149 | height: 33px; 150 | width: 120px; 151 | } 152 | a.openid-facebook { 153 | font-weight: bold; 154 | color: #424242; 155 | text-decoration: none; 156 | padding-right: 3px; 157 | } 158 | a.openid-gitlab { 159 | font-weight: bold; 160 | color: #424242; 161 | text-decoration: none; 162 | padding-right: 3px; 163 | } 164 | a.openid-github { 165 | font-weight: bold; 166 | color: #424242; 167 | text-decoration: none; 168 | padding-right: 3px; 169 | } 170 | a.openid-google-plus { 171 | font-weight: bold; 172 | color: #fff; 173 | text-decoration: none; 174 | padding-right: 3px; 175 | background-color: #dd4b39; 176 | } 177 | div.openid-info { 178 | float: left; 179 | padding: 20px; 180 | border: 1px solid #ccc; 181 | } 182 | div.openid-info dt { 183 | float: left; 184 | width: 100px; 185 | clear: left; 186 | } 187 | div.openid-info dd { 188 | float: left; 189 | padding-left: 20px; 190 | } 191 | div.openid-retry { 192 | float: left; 193 | clear: both; 194 | padding-top: 30px; 195 | } 196 | -------------------------------------------------------------------------------- /samples/web/atlas/css/awa.css: -------------------------------------------------------------------------------- 1 | /** 2 | * main.css - Main style for AWA pages 3 | * Copyright (C) 2011, 2012 Stephane Carrez 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | */ 6 | html, body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, textarea, p, blockquote, th, td, label { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | html, body { 11 | height: 100%; 12 | } 13 | body { 14 | font-family: Arial, Verdana, sans-serif; 15 | font-size: 14px; 16 | background-color: #f0f0f0; 17 | overflow-y: scroll; 18 | } 19 | /* IE 6 min-height hack */ 20 | * html #pam-page-content { 21 | height: 100%; 22 | } 23 | body.awa { 24 | background-color: #81CFB7; 25 | } 26 | 27 | #awa-page { 28 | position: relative; 29 | min-height:100%; 30 | margin-left: 1%; 31 | margin-right: 1%; 32 | background-color: white; 33 | margin-bottom: -30px; /* See #awa-clfooter height */ 34 | display: block; 35 | width: 98%; 36 | } 37 | .awa #awa-page { 38 | background-color: #99DFCA; 39 | } 40 | #awa-hdr { 41 | position: absolute; 42 | padding: 0; 43 | margin: 0; 44 | width: 100%; 45 | height: 25px; 46 | background-color: #333; /* #9BCFBA; */ 47 | border-bottom: 5px solid #333; /* #3E6F5C; */ 48 | z-index: 1; 49 | } 50 | #awa-hdr ul { 51 | float: right; 52 | list-style-type: none; 53 | padding-right: 20px; 54 | } 55 | #awa-hdr li { 56 | float: right; 57 | padding-left: 5px; 58 | padding-right: 5px; 59 | line-height: 24px; 60 | font-size: 16px; 61 | } 62 | #awa-hdr li a, #awa-hdr li { 63 | color: #ddd; 64 | font-size: 12px; 65 | text-decoration: none; 66 | border-bottom: 3px solid #333; 67 | } 68 | #awa-hdr li a:hover { 69 | color: #fff; 70 | border-bottom: 3px solid orange; 71 | } 72 | #awa-hdr #awa-top-nav { 73 | float: left; 74 | } 75 | #awa-content { 76 | padding-top: 50px; 77 | } 78 | .awa-ok { 79 | float: left; 80 | width: 48px; 81 | height: 48px; 82 | margin-left: 5px; 83 | margin-right: 15px; 84 | } 85 | #awa-footer { 86 | float: none; 87 | width: 100%; 88 | position: relative; 89 | bottom: 0; 90 | height: 2px; 91 | } 92 | #awa-copyright { 93 | float: left; 94 | margin-left: 20px; 95 | margin-right: 5px; 96 | } 97 | #awa-clfooter { 98 | clear: both; 99 | height: 30px; /* See #awa-page margin bottom */ 100 | width: 100%; 101 | } 102 | #awa-footer_content { 103 | float: right; 104 | margin-right: 20px; 105 | } 106 | ul.buttons, ul.awa-buttons { 107 | float: left; 108 | list-style-type: none; 109 | margin-top: 1em; 110 | margin-bottom: 1em; 111 | } 112 | 113 | li.awa-error label { 114 | color: red; 115 | } 116 | 117 | /** 118 | * AWA Errors 119 | */ 120 | span.awa-error, dl.awa-error dt label { 121 | color: red; 122 | } 123 | span.awa-error { 124 | font-size: 14px; 125 | margin-bottom: 2px; 126 | margin-top: 2px; 127 | height: auto; 128 | } 129 | 130 | /** 131 | * Form definition. 132 | */ 133 | div.awa-form { 134 | font-size: 16px; 135 | float: left; 136 | width: 100%; 137 | } 138 | 139 | /** 140 | * A form field (optional or required). 141 | * - dt contains the label 142 | * - dd contains the input field 143 | */ 144 | dl.awa-optional, dl.awa-required { 145 | width: 96%; 146 | padding-left: 2%; 147 | padding-right: 2%; 148 | } 149 | dl.awa-optional dt, dl.awa-required dt { 150 | float: left; 151 | width: 100%; 152 | padding-top: 8px; 153 | } 154 | dl.awa-optional dd, dl.awa-required dd { 155 | float: left; 156 | clear: both; 157 | width: 100%; 158 | } 159 | dl.awa-error dt { 160 | color: red; 161 | } 162 | dl.awa-error span.error { 163 | font-size: 0.9em; 164 | } 165 | dl.awa-optional label, dl.awa-required label { 166 | display: inline; 167 | float: left; 168 | clear: left; 169 | text-align: left; 170 | color: #555; 171 | font-weight: bold; 172 | padding-right: 5px; 173 | } 174 | dl.awa-optional input, dl.awa-required input { 175 | width: 98%; 176 | font-size: 16px; 177 | padding: 3px; 178 | } 179 | dl.awa-optional textarea, dl.awa-required textarea { 180 | width: 98%; 181 | font-size: 16px; 182 | padding: 3px; 183 | } 184 | 185 | /** 186 | * Error messages 187 | */ 188 | div.awa-messages { 189 | width: 96%; 190 | height: 43px; 191 | float: left; 192 | padding-top: 5px; 193 | padding-left: 2%; 194 | padding-right: 2%; 195 | } 196 | 197 | /** 198 | * A global error message. 199 | */ 200 | div.awa-error { 201 | width: 640px; 202 | margin: auto; 203 | font-size: 16px; 204 | } -------------------------------------------------------------------------------- /regtests/src/security-permissions-tests.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security-permissions-tests - Unit tests for Security.Permissions 3 | -- Copyright (C) 2011, 2012, 2016, 2018 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | with Util.Test_Caller; 8 | 9 | package body Security.Permissions.Tests is 10 | 11 | package Caller is new Util.Test_Caller (Test, "Security.Permissions"); 12 | 13 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite) is 14 | begin 15 | Caller.Add_Test (Suite, "Test Security.Permissions.Add_Permission", 16 | Test_Add_Permission'Access); 17 | Caller.Add_Test (Suite, "Test Security.Permissions.Get_Permission_Index", 18 | Test_Add_Permission'Access); 19 | Caller.Add_Test (Suite, "Test Security.Permissions.Definition", 20 | Test_Define_Permission'Access); 21 | Caller.Add_Test (Suite, "Test Security.Permissions.Get_Permission_Index (invalid name)", 22 | Test_Get_Invalid_Permission'Access); 23 | Caller.Add_Test (Suite, "Test Security.Permissions.Add_Permission (Set)", 24 | Test_Add_Permission_Set'Access); 25 | end Add_Tests; 26 | 27 | -- ------------------------------ 28 | -- Test Add_Permission and Get_Permission_Index 29 | -- ------------------------------ 30 | procedure Test_Add_Permission (T : in out Test) is 31 | Index1, Index2 : Permission_Index; 32 | begin 33 | Add_Permission ("test-create-permission", Index1); 34 | 35 | T.Assert (Index1 = Get_Permission_Index ("test-create-permission"), 36 | "Get_Permission_Index failed"); 37 | 38 | Add_Permission ("test-create-permission", Index2); 39 | 40 | T.Assert (Index2 = Index1, 41 | "Add_Permission failed"); 42 | 43 | end Test_Add_Permission; 44 | 45 | -- ------------------------------ 46 | -- Test the permission created by the Definition package. 47 | -- ------------------------------ 48 | procedure Test_Define_Permission (T : in out Test) is 49 | Index : Permission_Index; 50 | begin 51 | Index := Get_Permission_Index ("admin"); 52 | T.Assert (P_Admin.Permission = Index, "Invalid permission for admin"); 53 | 54 | Index := Get_Permission_Index ("create"); 55 | T.Assert (P_Create.Permission = Index, "Invalid permission for create"); 56 | 57 | Index := Get_Permission_Index ("update"); 58 | T.Assert (P_Update.Permission = Index, "Invalid permission for update"); 59 | 60 | Index := Get_Permission_Index ("delete"); 61 | T.Assert (P_Delete.Permission = Index, "Invalid permission for delete"); 62 | 63 | T.Assert (P_Admin.Permission /= P_Create.Permission, "Admin or create permission invalid"); 64 | T.Assert (P_Admin.Permission /= P_Update.Permission, "Admin or update permission invalid"); 65 | T.Assert (P_Admin.Permission /= P_Delete.Permission, "Admin or delete permission invalid"); 66 | T.Assert (P_Create.Permission /= P_Update.Permission, "Create or update permission invalid"); 67 | T.Assert (P_Create.Permission /= P_Delete.Permission, "Create or delete permission invalid"); 68 | T.Assert (P_Update.Permission /= P_Delete.Permission, "Create or delete permission invalid"); 69 | end Test_Define_Permission; 70 | 71 | -- ------------------------------ 72 | -- Test Get_Permission on invalid permission name. 73 | -- ------------------------------ 74 | procedure Test_Get_Invalid_Permission (T : in out Test) is 75 | Index : Permission_Index; 76 | 77 | pragma Unreferenced (Index); 78 | begin 79 | Index := Get_Permission_Index ("invalid"); 80 | Util.Tests.Fail (T, "No exception raised by Get_Permission_Index for an invalid name"); 81 | 82 | exception 83 | when Invalid_Name => 84 | null; 85 | end Test_Get_Invalid_Permission; 86 | 87 | -- ------------------------------ 88 | -- Test operations on the Permission_Index_Set. 89 | -- ------------------------------ 90 | procedure Test_Add_Permission_Set (T : in out Test) is 91 | Set : Permission_Index_Set := EMPTY_SET; 92 | begin 93 | T.Assert (not Has_Permission (Set, P_Update.Permission), 94 | "The update permission is not in the set"); 95 | Add_Permission (Set, P_Update.Permission); 96 | T.Assert (Has_Permission (Set, P_Update.Permission), 97 | "The update permission is in the set"); 98 | end Test_Add_Permission_Set; 99 | 100 | end Security.Permissions.Tests; 101 | -------------------------------------------------------------------------------- /src/security-oauth.ads: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-oauth -- OAuth Security 3 | -- Copyright (C) 2012, 2016, 2017, 2018, 2019, 2020 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | with Ada.Strings.Unbounded; 8 | 9 | -- = OAuth = 10 | -- The Security.OAuth package defines and implements the OAuth 2.0 authorization 11 | -- framework as defined by the IETF working group in RFC 6749: 12 | -- The OAuth 2.0 Authorization Framework. 13 | -- 14 | -- @include security-oauth-clients.ads 15 | -- @include security-oauth-servers.ads 16 | package Security.OAuth is 17 | 18 | -- OAuth 2.0: Section 10.2.2. Initial Registry Contents 19 | -- RFC 6749: 11.2.2. Initial Registry Contents 20 | CLIENT_ID : constant String := "client_id"; 21 | CLIENT_SECRET : constant String := "client_secret"; 22 | RESPONSE_TYPE : constant String := "response_type"; 23 | REDIRECT_URI : constant String := "redirect_uri"; 24 | SCOPE : constant String := "scope"; 25 | STATE : constant String := "state"; 26 | CODE : constant String := "code"; 27 | ERROR_DESCRIPTION : constant String := "error_description"; 28 | ERROR_URI : constant String := "error_uri"; 29 | GRANT_TYPE : constant String := "grant_type"; 30 | ACCESS_TOKEN : constant String := "access_token"; 31 | TOKEN_TYPE : constant String := "token_type"; 32 | EXPIRES_IN : constant String := "expires_in"; 33 | USERNAME : constant String := "username"; 34 | PASSWORD : constant String := "password"; 35 | REFRESH_TOKEN : constant String := "refresh_token"; 36 | 37 | NONCE_TOKEN : constant String := "nonce"; 38 | 39 | -- RFC 6749: 5.2. Error Response 40 | INVALID_REQUEST : aliased constant String := "invalid_request"; 41 | INVALID_CLIENT : aliased constant String := "invalid_client"; 42 | INVALID_GRANT : aliased constant String := "invalid_grant"; 43 | UNAUTHORIZED_CLIENT : aliased constant String := "unauthorized_client"; 44 | UNSUPPORTED_GRANT_TYPE : aliased constant String := "unsupported_grant_type"; 45 | INVALID_SCOPE : aliased constant String := "invalid_scope"; 46 | 47 | -- RFC 6749: 4.1.2.1. Error Response 48 | ACCESS_DENIED : aliased constant String := "access_denied"; 49 | UNSUPPORTED_RESPONSE_TYPE : aliased constant String := "unsupported_response_type"; 50 | SERVER_ERROR : aliased constant String := "server_error"; 51 | TEMPORARILY_UNAVAILABLE : aliased constant String := "temporarily_unavailable"; 52 | 53 | type Client_Authentication_Type is (AUTH_NONE, AUTH_BASIC); 54 | 55 | -- ------------------------------ 56 | -- Application 57 | -- ------------------------------ 58 | -- The Application holds the necessary information to let a user 59 | -- grant access to its protected resources on the resource server. It contains 60 | -- information that allows the OAuth authorization server to identify the 61 | -- application (client id and secret key). 62 | type Application is tagged private; 63 | 64 | -- Get the application identifier. 65 | function Get_Application_Identifier (App : in Application) return String; 66 | 67 | -- Set the application identifier used by the OAuth authorization server 68 | -- to identify the application (for example, the App ID in Facebook). 69 | procedure Set_Application_Identifier (App : in out Application; 70 | Client : in String); 71 | 72 | -- Set the application secret defined in the OAuth authorization server 73 | -- for the application (for example, the App Secret in Facebook). 74 | procedure Set_Application_Secret (App : in out Application; 75 | Secret : in String); 76 | 77 | -- Set the redirection callback that will be used to redirect the user 78 | -- back to the application after the OAuth authorization is finished. 79 | procedure Set_Application_Callback (App : in out Application; 80 | URI : in String); 81 | 82 | -- Set the client authentication method used when doing OAuth calls for this application. 83 | -- See RFC 6749, 2.3. Client Authentication 84 | procedure Set_Client_Authentication (App : in out Application; 85 | Method : in Client_Authentication_Type); 86 | 87 | private 88 | 89 | type Application is tagged record 90 | Client_Id : Ada.Strings.Unbounded.Unbounded_String; 91 | Secret : Ada.Strings.Unbounded.Unbounded_String; 92 | Callback : Ada.Strings.Unbounded.Unbounded_String; 93 | Client_Auth : Client_Authentication_Type := AUTH_NONE; 94 | end record; 95 | 96 | end Security.OAuth; 97 | -------------------------------------------------------------------------------- /regtests/src/security-oauth-jwt-tests.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security-oayth-jwt-tests - Unit tests for JSON Web Token 3 | -- Copyright (C) 2013, 2015 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | with Ada.Calendar.Formatting; 8 | 9 | with Util.Test_Caller; 10 | 11 | package body Security.OAuth.JWT.Tests is 12 | 13 | package Caller is new Util.Test_Caller (Test, "Security.OAuth.JWT"); 14 | 15 | -- A JWT token returned by Google+. 16 | K : constant String := "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVmOTBlMWExMGE4YzgwZWJhZWNmYzM4NzBjZDl" 17 | & "lMGVhMGI3ZDVmZGMifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXRfaGFzaCI6Im9Ka19EYnFvb1" 18 | & "FVc0FhY3k2cnkxeHciLCJhdWQiOiI4NzI2NTU5OTQwMTQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJz" 19 | & "dWIiOiIxMDgzNjA3MDMwOTk3MDg5Nzg4NzAiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJhenAiOiI4NzI2NT" 20 | & "U5OTQwMTQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6InN0ZXBoYW5lLmNhcnJlekBnbWFp" 21 | & "bC5jb20iLCJpYXQiOjEzNjg5NjgyMzMsImV4cCI6MTM2ODk3MjEzM30.UL1qp2wmleV-ED2A_hlqgDLIGgJB3f" 22 | & "_N7fiz1CgttJcwbmMVwhag3ox2WE9C1KwXhrjwT8eigZ0WkDys5WO1dYs2G1QbDZPnsYYMyHK9XpycaDMEKtVZ" 23 | & "C4C6DkB1SrBHbN0Tv6ExWpszzp1JEL8nZnHd3T_AA3paqONnkvQw_yo"; 24 | 25 | procedure Test_Operation (T : in out Test) is 26 | R : Token; 27 | begin 28 | R := Decode (K); 29 | Util.Tests.Assert_Equals (T, Value, Get (R), "Extraction failed"); 30 | end Test_Operation; 31 | 32 | procedure Test_Time_Operation (T : in out Test) is 33 | R : Token; 34 | begin 35 | R := Decode (K); 36 | Util.Tests.Assert_Equals (T, Value, Ada.Calendar.Formatting.Image (Get (R)), 37 | "Extraction failed"); 38 | end Test_Time_Operation; 39 | 40 | procedure Test_Get_Issuer is 41 | new Test_Operation (Get_Issuer, "accounts.google.com"); 42 | procedure Test_Get_Audience is 43 | new Test_Operation (Get_Audience, "872655994014.apps.googleusercontent.com"); 44 | procedure Test_Get_Subject is 45 | new Test_Operation (Get_Subject, "108360703099708978870"); 46 | procedure Test_Get_Authorized_Presenters is 47 | new Test_Operation (Get_Authorized_Presenters, "872655994014.apps.googleusercontent.com"); 48 | procedure Test_Get_Expiration is 49 | new Test_Time_Operation (Get_Expiration, "2013-05-19 14:02:13"); 50 | procedure Test_Get_Issued_At is 51 | new Test_Time_Operation (Get_Issued_At, "2013-05-19 12:57:13"); 52 | 53 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite) is 54 | begin 55 | Caller.Add_Test (Suite, "Test Security.OAuth.JWT.Decode", 56 | Test_Get_Issuer'Access); 57 | Caller.Add_Test (Suite, "Test Security.OAuth.JWT.Get_Issuer", 58 | Test_Get_Issuer'Access); 59 | Caller.Add_Test (Suite, "Test Security.OAuth.JWT.Get_Audience", 60 | Test_Get_Audience'Access); 61 | Caller.Add_Test (Suite, "Test Security.OAuth.JWT.Get_Subject", 62 | Test_Get_Subject'Access); 63 | Caller.Add_Test (Suite, "Test Security.OAuth.JWT.Get_Authorized_Presenters", 64 | Test_Get_Authorized_Presenters'Access); 65 | Caller.Add_Test (Suite, "Test Security.OAuth.JWT.Get_Expiration", 66 | Test_Get_Expiration'Access); 67 | Caller.Add_Test (Suite, "Test Security.OAuth.JWT.Get_Authentication_Time", 68 | Test_Get_Issued_At'Access); 69 | Caller.Add_Test (Suite, "Test Security.OAuth.JWT.Decode (error)", 70 | Test_Decode_Error'Access); 71 | 72 | end Add_Tests; 73 | 74 | -- ------------------------------ 75 | -- Test Decode operation with errors. 76 | -- ------------------------------ 77 | procedure Test_Decode_Error (T : in out Test) is 78 | K : constant String := "eyJhbxGciOiJSUzI1NiIsImtpZCI6IjVmOTBlMWExMGE4YzgwZWJhZWNmYzM4NzBjZDl" 79 | & "lMGVhMGI3ZDVmZGMifQ.eyJpc3xMiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXRfaGFzaCI6Im9Ka19EYnFvb1" 80 | & "FVc0FhY3k2cnkxeHciLCJhdWQiOiI4NzI2NTU5OTQwMTQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJz" 81 | & "dWIiOiIxMDgzNjA3MDMwOTk3MDg5Nzg4NzAiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJhenAiOiI4NzI2NT" 82 | & "U5OTQwMTQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6InN0ZXBoYW5lLmNhcnJlekBnbWFp" 83 | & "bC5jb20iLCJpYXQiOjEzNjg5NjgyMzMsImV4cCI6MTM2ODk3MjEzM30.UL1qp2wmleV-ED2A_hlqgDLIGgJB3f" 84 | & "_N7fiz1CgttJcwbmMVwhag3ox2WE9C1KwXhrjwT8eigZ0WkDys5WO1dYs2G1QbDZPnsYYMyHK9XpycaDMEKtVZ" 85 | & "C4C6DkB1SrBHbN0Tv6ExWpszzp1JEL8nZnHd3T_AA3paqONnkvQw_yx"; 86 | R : Token; 87 | pragma Unreferenced (R); 88 | begin 89 | R := Decode (K); 90 | T.Fail ("No exception raised"); 91 | T.Assert (False, "Bad"); 92 | 93 | exception 94 | when Invalid_Token => 95 | null; 96 | end Test_Decode_Error; 97 | 98 | end Security.OAuth.JWT.Tests; 99 | -------------------------------------------------------------------------------- /docs/Security_Auth.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | The `Security.Auth` package implements an authentication framework that is 3 | suitable for OpenID 2.0, OAuth 2.0 and later for OpenID Connect. It allows an application 4 | to authenticate users using an external authorization server such as Google, Facebook, 5 | Google +, Twitter and others. 6 | 7 | See [OpenID Authentication 2.0](https://openid.net/specs/openid-authentication-2_0.html) - Final 8 | https://openid.net/specs/openid-authentication-2_0.html 9 | 10 | See [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) 11 | https://openid.net/specs/openid-connect-core-1_0.html 12 | 13 | See Facebook API: The Login Flow for Web (without JavaScript SDK) 14 | https://developers.facebook.com/docs/facebook-login/login-flow-for-web-no-jssdk/ 15 | 16 | Despite their subtle differences, all these authentication frameworks share almost 17 | a common flow. The API provided by `Security.Auth` defines an abstraction suitable 18 | for all these frameworks. 19 | 20 | There are basically two steps that an application must implement: 21 | 22 | * `Discovery`: to resolve and use the OpenID provider and redirect the user to the 23 | provider authentication form. 24 | * `Verify`: to decode the authentication and check its result. 25 | 26 | ![](images/OpenID.png) 27 | 28 | The authentication process is the following: 29 | 30 | * The application should redirect the user to the authentication URL. 31 | * The OpenID provider authenticate the user and redirects the user to the callback CB. 32 | * The association is decoded from the callback parameter. 33 | * The `Verify` procedure is called with the association to check the result and 34 | obtain the authentication results. 35 | 36 | ## Initialization 37 | The initialization process must be done before each two steps (discovery and verify). 38 | The Authentication manager must be declared and configured. 39 | 40 | ```Ada 41 | Mgr : Security.Auth.Manager; 42 | ``` 43 | 44 | For the configuration, the Initialize procedure is called to configure 45 | the Auth realm and set the authentication return callback URL. The return callback 46 | must be a valid URL that is based on the realm. Example: 47 | 48 | ```Ada 49 | Mgr.Initialize (Name => "http://app.site.com/auth", 50 | Return_To => "http://app.site.com/auth/verify", 51 | Realm => "openid"); 52 | ``` 53 | 54 | After this initialization, the authentication manager can be used in the authentication 55 | process. 56 | 57 | ## OpenID Configuration 58 | The Open ID provider needs the following configuration parameters: 59 | 60 | ```Ada 61 | openid.realm The OpenID realm parameter passed in the authentication URL. 62 | openid.callback_url The OpenID return_to parameter. 63 | ``` 64 | 65 | ### Google+ 66 | The Google+ authentication is based on OAuth 2.0 and the OpenID Connect Basic Client Profile. 67 | 68 | See https://developers.google.com/accounts/docs/OAuth2Login 69 | 70 | ## Discovery: creating the authentication URL 71 | The first step is to create an authentication URL to which the user must be redirected. 72 | In this step, we have to create an OpenID manager, discover the OpenID provider, 73 | do the association and get an End_Point. The OpenID provider is specified as an 74 | URL, below is an example for Google OpenID: 75 | 76 | ```Ada 77 | Provider : constant String := "https://www.google.com/accounts/o8/id"; 78 | OP : Security.Auth.End_Point; 79 | Assoc : constant Security.Auth.Association_Access := new Security.Auth.Association; 80 | ``` 81 | 82 | The following steps are performed: 83 | 84 | * The Discover procedure is called to retrieve from the OpenID provider the XRDS 85 | stream and identify the provider. An End_Point is returned in OP. 86 | * The Associate procedure is called to make the association with the End_Point. 87 | The Association record holds session, and authentication. 88 | 89 | ```Ada 90 | Mgr.Discover (Provider, OP); -- Yadis discovery (get the XRDS file). 91 | Mgr.Associate (OP, Assoc.all);-- Associate and get an end-point with a key. 92 | ``` 93 | 94 | After this first step, you must manage to save the association in the HTTP session. 95 | Then you must redirect to the authentication URL that is obtained by using: 96 | 97 | ```Ada 98 | Auth_URL : constant String := Mgr.Get_Authentication_URL (OP, Assoc.all); 99 | ``` 100 | 101 | ## Verify: acknowledge the authentication in the callback URL 102 | The second step is done when the user has finished the authentication successfully or not. 103 | For this step, the application must get back the association that was saved in the session. 104 | It must also prepare a parameters object that allows the OpenID framework to get the 105 | URI parameters from the return callback. 106 | 107 | ```Ada 108 | Assoc : Association_Access := ...; -- Get the association saved in the session. 109 | Credential : Security.Auth.Authentication; 110 | Params : Auth_Params; 111 | ``` 112 | 113 | The auth manager must be initialized and the Verify procedure is called with 114 | the association, parameters and the authentication result. The Get_Status function 115 | must be used to check that the authentication succeeded. 116 | 117 | ```Ada 118 | Mgr.Verify (Assoc.all, Params, Credential); 119 | if Security.Auth.Get_Status (Credential) = Security.Auth.AUTHENTICATED then ... -- Success. 120 | ``` 121 | 122 | ## Principal creation 123 | After the user is successfully authenticated, a user principal can be created and saved in 124 | the session. The user principal can then be used to assign permissions to that user and 125 | enforce the application permissions using the security policy manger. 126 | 127 | ```Ada 128 | P : Security.Auth.Principal_Access := Security.Auth.Create_Principal (Credential); 129 | ``` 130 | 131 | -------------------------------------------------------------------------------- /docs/pandoc.css: -------------------------------------------------------------------------------- 1 | /* 2 | * I add this to html files generated with pandoc. 3 | */ 4 | 5 | html { 6 | font-size: 100%; 7 | overflow-y: scroll; 8 | -webkit-text-size-adjust: 100%; 9 | -ms-text-size-adjust: 100%; 10 | } 11 | 12 | body { 13 | color: #444; 14 | font-family: Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman', serif; 15 | font-size: 12px; 16 | line-height: 1.7; 17 | padding: 1em; 18 | margin: auto; 19 | max-width: 60em; 20 | background: #fefefe; 21 | } 22 | 23 | a { 24 | color: #0645ad; 25 | text-decoration: none; 26 | } 27 | 28 | a:visited { 29 | color: #0b0080; 30 | } 31 | 32 | a:hover { 33 | color: #06e; 34 | } 35 | 36 | a:active { 37 | color: #faa700; 38 | } 39 | 40 | a:focus { 41 | outline: thin dotted; 42 | } 43 | 44 | *::-moz-selection { 45 | background: rgba(255, 255, 0, 0.3); 46 | color: #000; 47 | } 48 | 49 | *::selection { 50 | background: rgba(255, 255, 0, 0.3); 51 | color: #000; 52 | } 53 | 54 | a::-moz-selection { 55 | background: rgba(255, 255, 0, 0.3); 56 | color: #0645ad; 57 | } 58 | 59 | a::selection { 60 | background: rgba(255, 255, 0, 0.3); 61 | color: #0645ad; 62 | } 63 | 64 | p { 65 | margin: 1em 0; 66 | } 67 | 68 | img { 69 | max-width: 100%; 70 | } 71 | 72 | h1, h2, h3, h4, h5, h6 { 73 | color: #111; 74 | line-height: 125%; 75 | margin-top: 2em; 76 | font-weight: normal; 77 | } 78 | 79 | h4, h5, h6 { 80 | font-weight: bold; 81 | } 82 | 83 | h1 { 84 | font-size: 2.5em; 85 | } 86 | 87 | h2 { 88 | font-size: 2em; 89 | } 90 | 91 | h3 { 92 | font-size: 1.5em; 93 | } 94 | 95 | h4 { 96 | font-size: 1.2em; 97 | } 98 | 99 | h5 { 100 | font-size: 1em; 101 | } 102 | 103 | h6 { 104 | font-size: 0.9em; 105 | } 106 | 107 | blockquote { 108 | color: #666666; 109 | margin: 0; 110 | padding-left: 3em; 111 | border-left: 0.5em #EEE solid; 112 | } 113 | 114 | hr { 115 | display: block; 116 | height: 2px; 117 | border: 0; 118 | border-top: 1px solid #aaa; 119 | border-bottom: 1px solid #eee; 120 | margin: 1em 0; 121 | padding: 0; 122 | } 123 | 124 | pre, code, kbd, samp { 125 | color: #000; 126 | font-family: monospace, monospace; 127 | _font-family: 'courier new', monospace; 128 | font-size: 0.98em; 129 | } 130 | 131 | pre { 132 | white-space: pre; 133 | white-space: pre-wrap; 134 | word-wrap: break-word; 135 | } 136 | 137 | b, strong { 138 | font-weight: bold; 139 | } 140 | 141 | dfn { 142 | font-style: italic; 143 | } 144 | 145 | ins { 146 | background: #ff9; 147 | color: #000; 148 | text-decoration: none; 149 | } 150 | 151 | mark { 152 | background: #ff0; 153 | color: #000; 154 | font-style: italic; 155 | font-weight: bold; 156 | } 157 | 158 | sub, sup { 159 | font-size: 75%; 160 | line-height: 0; 161 | position: relative; 162 | vertical-align: baseline; 163 | } 164 | 165 | sup { 166 | top: -0.5em; 167 | } 168 | 169 | sub { 170 | bottom: -0.25em; 171 | } 172 | 173 | ul, ol { 174 | margin: 1em 0; 175 | padding: 0 0 0 2em; 176 | } 177 | 178 | li p:last-child { 179 | margin-bottom: 0; 180 | } 181 | 182 | ul ul, ol ol { 183 | margin: .3em 0; 184 | } 185 | 186 | dl { 187 | margin-bottom: 1em; 188 | } 189 | 190 | dt { 191 | font-weight: bold; 192 | margin-bottom: .8em; 193 | } 194 | 195 | dd { 196 | margin: 0 0 .8em 2em; 197 | } 198 | 199 | dd:last-child { 200 | margin-bottom: 0; 201 | } 202 | 203 | img { 204 | border: 0; 205 | -ms-interpolation-mode: bicubic; 206 | vertical-align: middle; 207 | } 208 | 209 | figure { 210 | display: block; 211 | text-align: center; 212 | margin: 1em 0; 213 | } 214 | 215 | figure img { 216 | border: none; 217 | margin: 0 auto; 218 | } 219 | 220 | figcaption { 221 | font-size: 0.8em; 222 | font-style: italic; 223 | margin: 0 0 .8em; 224 | } 225 | 226 | table { 227 | margin-bottom: 2em; 228 | border-bottom: 1px solid #ddd; 229 | border-right: 1px solid #ddd; 230 | border-spacing: 0; 231 | border-collapse: collapse; 232 | } 233 | 234 | table th { 235 | padding: .2em 1em; 236 | background-color: #eee; 237 | border-top: 1px solid #ddd; 238 | border-left: 1px solid #ddd; 239 | } 240 | 241 | table td { 242 | padding: .2em 1em; 243 | border-top: 1px solid #ddd; 244 | border-left: 1px solid #ddd; 245 | vertical-align: top; 246 | } 247 | 248 | .author { 249 | font-size: 1.2em; 250 | text-align: center; 251 | } 252 | 253 | @media only screen and (min-width: 480px) { 254 | body { 255 | font-size: 14px; 256 | } 257 | } 258 | @media only screen and (min-width: 768px) { 259 | body { 260 | font-size: 16px; 261 | } 262 | } 263 | @media print { 264 | * { 265 | background: transparent !important; 266 | color: black !important; 267 | filter: none !important; 268 | -ms-filter: none !important; 269 | } 270 | 271 | body { 272 | font-size: 12pt; 273 | max-width: 100%; 274 | } 275 | 276 | a, a:visited { 277 | text-decoration: underline; 278 | } 279 | 280 | hr { 281 | height: 1px; 282 | border: 0; 283 | border-bottom: 1px solid black; 284 | } 285 | 286 | a[href]:after { 287 | content: " (" attr(href) ")"; 288 | } 289 | 290 | abbr[title]:after { 291 | content: " (" attr(title) ")"; 292 | } 293 | 294 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { 295 | content: ""; 296 | } 297 | 298 | pre, blockquote { 299 | border: 1px solid #999; 300 | padding-right: 1em; 301 | page-break-inside: avoid; 302 | } 303 | 304 | tr, img { 305 | page-break-inside: avoid; 306 | } 307 | 308 | img { 309 | max-width: 100% !important; 310 | } 311 | 312 | @page :left { 313 | margin: 15mm 20mm 15mm 10mm; 314 | } 315 | 316 | @page :right { 317 | margin: 15mm 10mm 15mm 20mm; 318 | } 319 | 320 | p, h2, h3 { 321 | orphans: 3; 322 | widows: 3; 323 | } 324 | 325 | h2, h3 { 326 | page-break-after: avoid; 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /docs/Security.md: -------------------------------------------------------------------------------- 1 | # Security 2 | The `Security` package provides a security framework that allows 3 | an application to use OpenID or OAuth security frameworks. This security 4 | framework was first developed within the [Ada Server Faces](https://github.com/stcarrez/ada-asf) project. 5 | It was moved to a separate project so that it can easily be used with [AWS](https://github.com/AdaCore/aws). 6 | This package defines abstractions that are close or similar to Java 7 | security package. 8 | 9 | The security framework uses the following abstractions: 10 | 11 | * **Policy and policy manager**: 12 | The `Policy` defines and implements the set of security rules that specify how to 13 | protect the system or resources. The `Policy_Manager` maintains the security policies. 14 | 15 | * **Principal**: 16 | The `Principal` is the entity that can be authenticated. A principal is obtained 17 | after successful authentication of a user or of a system through an authorization process. 18 | The OpenID or OAuth authentication processes generate such security principal. 19 | 20 | * **Permission**: 21 | The `Permission` represents an access to a system or application resource. 22 | A permission is checked by using the security policy manager. The policy manager uses a 23 | security controller to enforce the permission. 24 | 25 | The `Security_Context` holds the contextual information that the security controller 26 | can use to verify the permission. The security context is associated with a principal and 27 | a set of policy context. 28 | 29 | ## Overview 30 | An application will create a security policy manager and register one or several security 31 | policies (yellow). The framework defines a simple role based security policy and an URL 32 | security policy intended to provide security in web applications. The security policy manager 33 | reads some security policy configuration file which allows the security policies to configure 34 | and create the security controllers. These controllers will enforce the security according 35 | to the application security rules. All these components are built only once when 36 | an application starts. 37 | 38 | A user is authenticated through an authentication system which creates a `Principal` 39 | instance that identifies the user (green). The security framework provides two authentication 40 | systems: OpenID and OAuth 2.0 OpenID Connect. 41 | 42 | ![](images/ModelOverview.png) 43 | 44 | When a permission must be enforced, a security context is created and linked to the 45 | `Principal` instance (blue). Additional security policy context can be added depending 46 | on the application context. To check the permission, the security policy manager is called 47 | and it will ask a security controller to verify the permission. 48 | 49 | The framework allows an application to plug its own security policy, its own policy context, 50 | its own principal and authentication mechanism. 51 | 52 | ## Permission 53 | The Security.Permissions package defines the different permissions that can be 54 | checked by the access control manager. An application should declare each permission 55 | by instantiating the Definition package: 56 | 57 | ```Ada 58 | package Perm_Create_Workspace is new Security.Permissions.Definition ("create-workspace"); 59 | ``` 60 | 61 | This declares a permission that can be represented by "create-workspace" in 62 | configuration files. In Ada, the permission is used as follows: 63 | 64 | ```Ada 65 | Perm_Create_Workspace.Permission 66 | ``` 67 | 68 | ## Principal 69 | A principal is created by using either the [Security_Auth OpenID], 70 | the [Security_OAuth OAuth] or another authentication mechanism. The authentication produces 71 | an object that must implement the `Principal` interface. For example: 72 | 73 | ```Ada 74 | P : Security.Auth.Principal_Access := Security.Auth.Create_Principal (Auth); 75 | ``` 76 | 77 | or 78 | 79 | ```Ada 80 | P : Security.OAuth.Clients.Access_Token_Access := Security.OAuth.Clients.Create_Access_Token 81 | ``` 82 | 83 | The principal is then stored in a security context. 84 | 85 | ## Security Context 86 | The security context provides contextual information for a security controller to 87 | verify that a permission is granted. 88 | This security context is used as follows: 89 | 90 | * An instance of the security context is declared within a function/procedure as 91 | a local variable. This instance will be associated internally with the current thread 92 | through a task attribute. 93 | * The security context is populated with information to identify the current user, 94 | his roles, permissions and other information that could be used by security controllers. 95 | * To verify a permission, the current security context is retrieved and the 96 | Has_Permission operation is called. This operation will use the security manager 97 | to find the security controller associated with the permission to verify. 98 | * The security controller will be called with the security context to check the permission. 99 | The whole job of checking the permission is done by the security controller or its 100 | associated policy manager. The security controller retrieves information from the 101 | security context to decide whether the permission is granted or not. 102 | 103 | For example the security context is declared as follows: 104 | 105 | ```Ada 106 | Context : Security.Contexts.Security_Context; 107 | ``` 108 | 109 | A security policy and a principal must be set in the security context. The security policy 110 | defines the rules that govern the security and the principal identifies the current user. 111 | 112 | ```Ada 113 | Context.Set_Context (Policy_Manager, P); 114 | ``` 115 | 116 | A permission is checked by using the Has_Permission operation: 117 | 118 | ```Ada 119 | if Security.Contexts.Has_Permission (Perm_Create_Workspace.Permission) then 120 | -- Granted 121 | else 122 | -- Denied 123 | end if; 124 | ``` 125 | 126 | 127 | -------------------------------------------------------------------------------- /docs/Security_OAuth.md: -------------------------------------------------------------------------------- 1 | # OAuth 2 | The Security.OAuth package defines and implements the OAuth 2.0 authorization 3 | framework as defined by the IETF working group in [RFC 6749](https://tools.ietf.org/html/rfc6749): 4 | The OAuth 2.0 Authorization Framework. 5 | 6 | ## OAuth Client 7 | The `Security.OAuth.Clients` package implements the client OAuth 2.0 authorization. 8 | 9 | ### Application setup 10 | For an OAuth2 client application to authenticate, it must be registered on the server 11 | and the server provides the following information: 12 | 13 | * **client_id**: the client identifier is a unique string that identifies the application. 14 | * **client_secret** the client secret is a secret shared between the server and the 15 | client application. The client secret is optional. 16 | 17 | The `Security.OAuth.Clients.Application` tagged record is the primary type that 18 | allows to perform one of the OAuth 2.0 authorization flows. It is necessary to 19 | declare an `Application` instance and register the **client_id**, the **client_secret** 20 | and the authorisation URLs to connect to the server. 21 | 22 | ```Ada 23 | App : Security.OAuth.Clients.Application; 24 | ... 25 | App.Set_Application_Identifier ("app-identifier"); 26 | App.Set_Application_Secret ("app-secret"); 27 | App.Set_Provider_URL ("https://graph.facebook.com/oauth/access_token"); 28 | 29 | ``` 30 | 31 | ### Resource Owner Password Credentials Grant 32 | The [RFC 6749](https://tools.ietf.org/html/rfc6749): 4.3. Resource Owner Password Credentials Grant allows to authorize an 33 | application by using the user's name and password. This is the simplest OAuth flow 34 | but because it requires to know the user's name and password, it is not recommended and 35 | not supported by several servers. To use this authorization, the application will use 36 | the `Request_Token` procedure and will give the user's name, password and the scope 37 | of permissions. When the authorization succeeds, a `Grant_Type` token object is returned. 38 | 39 | ```Ada 40 | Token : Security.OAuth.Clients.Grant_Type; 41 | ... 42 | App.Request_Token ("admin", "admin", "scope", Token); 43 | ``` 44 | 45 | ### Refreshing an access token 46 | An access token has an expiration date and a new access token must be asked by using the 47 | refresh token. When the access token has expired, the grant token object can be refreshed 48 | to retrieve a new access token by using the `Refresh_Token` procedure. The scope of 49 | permissions can also be passed. 50 | 51 | ```Ada 52 | App.Refresh_Token ("scope", Token); 53 | ``` 54 | 55 | ## OAuth Server 56 | OAuth server side is provided by the Security.OAuth.Servers package. 57 | This package allows to implement the authorization framework described in [RFC 6749](https://tools.ietf.org/html/rfc6749) 58 | "The OAuth 2.0 Authorization Framework". 59 | 60 | The authorization method produces a Grant_Type object that contains the result 61 | of the grant (successful or denied). It is the responsibility of the caller to format 62 | the result in JSON/XML and return it to the client. 63 | 64 | Three important operations are defined for the OAuth 2.0 framework. They will be used 65 | in the following order: 66 | 67 | Authorize is used to obtain an authorization request. This operation is 68 | optional in the OAuth 2.0 framework since some authorization method directly return 69 | the access token. This operation is used by the "Authorization Code Grant" and the 70 | "Implicit Grant". 71 | 72 | Token is used to get the access token and optional refresh token. Each time it 73 | is called, a new token is generated. 74 | 75 | Authenticate is used for the API request to verify the access token 76 | and authenticate the API call. This operation can be called several times with the same 77 | token until the token is revoked or it has expired. 78 | 79 | Several grant types are supported. 80 | 81 | ### Application Manager 82 | The application manager maintains the repository of applications which are known by 83 | the server and which can request authorization. Each application is identified by 84 | a client identifier (represented by the client_id request parameter). 85 | The application defines the authorization methods which are allowed as well as 86 | the parameters to control and drive the authorization. This includes the redirection 87 | URI, the application secret, the expiration delay for the access token. 88 | 89 | The application manager is implemented by the application server and it must 90 | implement the Application_Manager interface with the Find_Application 91 | method. The Find_Application is one of the first call made during the 92 | authenticate and token generation phases. 93 | 94 | ### Resource Owner Password Credentials Grant 95 | The password grant is one of the easiest grant method to understand but it is also one 96 | of the less secure. In this grant method, the username and user password are passed in 97 | the request parameter together with the application client identifier. The realm verifies 98 | the username and password and when they are correct it generates the access token with 99 | an optional refresh token. The realm also returns in the grant the user principal that 100 | identifies the user. 101 | 102 | ```Ada 103 | Realm : Security.OAuth.Servers.Auth_Manager; 104 | Grant : Security.OAuth.Servers.Grant_Type; 105 | Realm.Token (Params, Grant); 106 | 107 | ``` 108 | 109 | ### Accessing Protected Resources 110 | When accessing a protected resource, the API implementation will use the 111 | Authenticate operation to verify the access token and get a security principal. 112 | The security principal will identify the resource owner as well as the application 113 | that is doing the call. 114 | 115 | ```Ada 116 | Realm : Security.OAuth.Servers.Auth_Manager; 117 | Grant : Security.OAuth.Servers.Grant_Type; 118 | Token : String := ...; 119 | Realm.Authenticate (Token, Grant); 120 | 121 | ``` 122 | 123 | When a security principal is returned, the access token was validated and the 124 | request is granted for the application. 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/security-auth-oauth.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-oauth -- OAuth based authentication 3 | -- Copyright (C) 2013, 2017, 2020 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Util.Log.Loggers; 9 | package body Security.Auth.OAuth is 10 | 11 | Log : constant Util.Log.Loggers.Logger := Util.Log.Loggers.Create ("Security.Auth.OAuth"); 12 | 13 | -- ------------------------------ 14 | -- OAuth Manager 15 | -- ------------------------------ 16 | -- The Manager provides the core operations for the OAuth authorization process. 17 | 18 | -- ------------------------------ 19 | -- Initialize the authentication realm. 20 | -- ------------------------------ 21 | overriding 22 | procedure Initialize (Realm : in out Manager; 23 | Params : in Parameters'Class; 24 | Provider : in String := PROVIDER_OPENID) is 25 | begin 26 | Realm.App.Set_Application_Identifier (Params.Get_Parameter (Provider & ".client_id")); 27 | Realm.App.Set_Application_Secret (Params.Get_Parameter (Provider & ".secret")); 28 | Realm.App.Set_Application_Callback (Params.Get_Parameter (Provider & ".callback_url")); 29 | Realm.App.Set_Provider_URI (Params.Get_Parameter (Provider & ".request_url")); 30 | Realm.Realm := To_Unbounded_String (Params.Get_Parameter (Provider & ".realm")); 31 | Realm.Scope := To_Unbounded_String (Params.Get_Parameter (Provider & ".scope")); 32 | Realm.Issuer := To_Unbounded_String (Params.Get_Parameter (Provider & ".issuer")); 33 | end Initialize; 34 | 35 | -- ------------------------------ 36 | -- Discover the OpenID provider that must be used to authenticate the user. 37 | -- The Name can be an URL or an alias that identifies the provider. 38 | -- A cached OpenID provider can be returned. 39 | -- Read the XRDS document from the URI and initialize the OpenID provider end point. 40 | -- (See OpenID Section 7.3 Discovery) 41 | -- ------------------------------ 42 | overriding 43 | procedure Discover (Realm : in out Manager; 44 | Name : in String; 45 | Result : out End_Point) is 46 | pragma Unreferenced (Realm); 47 | begin 48 | Result.URL := To_Unbounded_String (Name); 49 | end Discover; 50 | 51 | -- ------------------------------ 52 | -- Associate the application (relying party) with the OpenID provider. 53 | -- The association can be cached. 54 | -- (See OpenID Section 8 Establishing Associations) 55 | -- ------------------------------ 56 | overriding 57 | procedure Associate (Realm : in out Manager; 58 | OP : in End_Point; 59 | Result : out Association) is 60 | pragma Unreferenced (Realm, OP); 61 | begin 62 | Result.Assoc_Handle := To_Unbounded_String (Security.OAuth.Clients.Create_Nonce (128)); 63 | Result.Nonce := To_Unbounded_String (Security.OAuth.Clients.Create_Nonce (256)); 64 | end Associate; 65 | 66 | -- ------------------------------ 67 | -- Get the authentication URL to which the user must be redirected for authentication 68 | -- by the authentication server. 69 | -- ------------------------------ 70 | overriding 71 | function Get_Authentication_URL (Realm : in Manager; 72 | OP : in End_Point; 73 | Assoc : in Association) return String is 74 | Result : Unbounded_String := OP.URL; 75 | State : constant String := Realm.App.Get_State (To_String (Assoc.Assoc_Handle)); 76 | Params : constant String := Realm.App.Get_Auth_Params (State, To_String (Realm.Scope)); 77 | begin 78 | if Index (Result, "?") > 0 then 79 | Append (Result, "&"); 80 | else 81 | Append (Result, "?"); 82 | end if; 83 | Append (Result, Params); 84 | Append (Result, "&"); 85 | Append (Result, Security.OAuth.RESPONSE_TYPE); 86 | Append (Result, "=code"); 87 | Append (Result, "&"); 88 | Append (Result, Security.OAuth.NONCE_TOKEN); 89 | Append (Result, "="); 90 | Append (Result, Assoc.Nonce); 91 | 92 | Log.Debug ("Params = {0}", Params); 93 | return To_String (Result); 94 | end Get_Authentication_URL; 95 | 96 | -- ------------------------------ 97 | -- Verify the authentication result 98 | -- ------------------------------ 99 | overriding 100 | procedure Verify (Realm : in out Manager; 101 | Assoc : in Association; 102 | Request : in Parameters'Class; 103 | Result : out Authentication) is 104 | State : constant String := Request.Get_Parameter (Security.OAuth.STATE); 105 | Code : constant String := Request.Get_Parameter (Security.OAuth.CODE); 106 | Error : constant String := Request.Get_Parameter (Security.OAuth.ERROR_DESCRIPTION); 107 | begin 108 | if Error'Length /= 0 then 109 | Set_Result (Result, CANCEL, "Authentication refused: " & Error); 110 | return; 111 | end if; 112 | 113 | -- First, verify that the state parameter matches our internal state. 114 | if not Realm.App.Is_Valid_State (To_String (Assoc.Assoc_Handle), State) then 115 | Set_Result (Result, INVALID_SIGNATURE, "invalid OAuth state parameter"); 116 | return; 117 | end if; 118 | 119 | -- Get the access token from the authorization code. 120 | declare 121 | use type Security.OAuth.Clients.Access_Token_Access; 122 | 123 | Acc : constant Security.OAuth.Clients.Access_Token_Access 124 | := Realm.App.Request_Access_Token (Code); 125 | begin 126 | if Acc = null then 127 | Set_Result (Result, INVALID_SIGNATURE, "cannot change the code to an access_token"); 128 | return; 129 | end if; 130 | 131 | -- Last step, verify the access token and get the user identity. 132 | Manager'Class (Realm).Verify_Access_Token (Assoc, Request, Acc, Result); 133 | end; 134 | 135 | end Verify; 136 | 137 | end Security.Auth.OAuth; 138 | -------------------------------------------------------------------------------- /docs/Security_Policies.md: -------------------------------------------------------------------------------- 1 | # Security Policies 2 | The Security Policy defines and implements the set of security rules that specify 3 | how to protect the system or resources. The `Policy_Manager` maintains 4 | the security policies. These policies are registered when an application starts, 5 | before reading the policy configuration files. 6 | 7 | ![](images/PolicyModel.png) 8 | 9 | While the policy configuration files are processed, the policy instances that have been 10 | registered will create a security controller and bind it to a given permission. After 11 | successful initialization, the `Policy_Manager` contains a list of securiy 12 | controllers which are associated with each permission defined by the application. 13 | 14 | ## Authenticated Permission 15 | The `auth-permission` is a pre-defined permission that can be configured in the XML 16 | configuration. Basically the permission is granted if the security context has a principal. 17 | Otherwise the permission is denied. The permission is assigned a name and is declared 18 | as follows: 19 | 20 | ```Ada 21 | 22 | 23 | view-profile 24 | 25 | 26 | ``` 27 | 28 | This example defines the `view-profile` permission. 29 | 30 | ## Grant Permission 31 | The `grant-permission` is another pre-defined permission that gives the permission whatever 32 | the security context. The permission is defined as follows: 33 | 34 | ```Ada 35 | 36 | 37 | anonymous 38 | 39 | 40 | ``` 41 | 42 | This example defines the `anonymous` permission. 43 | 44 | ## Role Based Security Policy 45 | The `Security.Policies.Roles` package implements a role based security policy. 46 | In this policy, users are assigned one or several roles and permissions are 47 | associated with roles. A permission is granted if the user has one of the roles required 48 | by the permission. 49 | 50 | ### Policy creation 51 | An instance of the `Role_Policy` must be created and registered in the policy manager. 52 | Get or declare the following variables: 53 | 54 | ```Ada 55 | Manager : Security.Policies.Policy_Manager; 56 | Policy : Security.Policies.Roles.Role_Policy_Access; 57 | ``` 58 | 59 | Create the role policy and register it in the policy manager as follows: 60 | 61 | ```Ada 62 | Policy := new Role_Policy; 63 | Manager.Add_Policy (Policy.all'Access); 64 | ``` 65 | 66 | ### Policy Configuration 67 | A role is represented by a name in security configuration files. A role based permission 68 | is associated with a list of roles. The permission is granted if the user has one of these 69 | roles. When the role based policy is registered in the policy manager, the following 70 | XML configuration is used: 71 | 72 | ```Ada 73 | 74 | 75 | admin 76 | 77 | 78 | manager 79 | 80 | 81 | create-workspace 82 | admin 83 | manager 84 | 85 | ... 86 | 87 | ``` 88 | 89 | This definition declares two roles: `admin` and `manager` 90 | It defines a permission `create-workspace` that will be granted if the 91 | user has either the `admin` or the `manager` role. 92 | 93 | Each role is identified by a name in the configuration file. It is represented by 94 | a `Role_Type`. To provide an efficient implementation, the `Role_Type` 95 | is represented as an integer with a limit of 64 different roles. 96 | 97 | ### Assigning roles to users 98 | A `Security_Context` must be associated with a set of roles before checking the 99 | permission. This is done by using the `Set_Role_Context` operation: 100 | 101 | ```Ada 102 | Security.Policies.Roles.Set_Role_Context (Security.Contexts.Current, "admin"); 103 | ``` 104 | 105 | ## URL Security Policy 106 | The `Security.Policies.Urls` implements a security policy intended to be used 107 | in web servers. It allows to protect an URL by defining permissions that must be granted 108 | for a user to get access to the URL. A typical example is a web server that has a set of 109 | administration pages, these pages should be accessed by users having some admin permission. 110 | 111 | ### Policy creation 112 | An instance of the `URL_Policy` must be created and registered in the policy manager. 113 | Get or declare the following variables: 114 | 115 | ```Ada 116 | Manager : Security.Policies.Policy_Manager; 117 | Policy : Security.Policies.Urls.URL_Policy_Access; 118 | ``` 119 | 120 | Create the URL policy and register it in the policy manager as follows: 121 | 122 | ```Ada 123 | Policy := new URL_Policy; 124 | Manager.Add_Policy (Policy.all'Access); 125 | ``` 126 | 127 | ### Policy Configuration 128 | Once the URL policy is registered, the policy manager can read and process the following 129 | XML configuration: 130 | 131 | ```Ada 132 | 133 | 134 | create-workspace 135 | admin 136 | /workspace/create 137 | /workspace/setup/* 138 | 139 | ... 140 | 141 | ``` 142 | 143 | This policy gives access to the URL that match one of the URL pattern if the 144 | security context has the permission `create-workspace` or `admin`. 145 | These two permissions are checked according to another security policy. 146 | The XML configuration can define several `url-policy`. When the `order` 147 | attribute is not defined, they are checked in the order defined in 148 | the XML. In other words, the first `url-policy` that matches the URL is used 149 | to verify the permission. When the `order` attribute is set to `first`, 150 | the rule is added at begining of the list. This special case is intended to 151 | allow overriding a previous definition in some configuration file. 152 | 153 | The `url-policy` definition can contain several `permission`. 154 | The first permission that is granted gives access to the URL. 155 | 156 | ### Checking for permission 157 | To check a URL permission, you must declare a `URL_Permission` object with the URL. 158 | 159 | ```Ada 160 | URL : constant String := ...; 161 | Perm : constant Policies.URLs.URL_Permission (URL'Length) 162 | := URL_Permission '(Len => URI'Length, URL => URL); 163 | ``` 164 | 165 | Then, we can check the permission: 166 | 167 | ```Ada 168 | Result : Boolean := Security.Contexts.Has_Permission (Perm); 169 | ``` 170 | 171 | -------------------------------------------------------------------------------- /samples/web/atlas/css/grids/fluid_grid.css: -------------------------------------------------------------------------------- 1 | /* 2 | Variable Grid System (Fluid Version). 3 | Learn more ~ http://www.spry-soft.com/grids/ 4 | Based on 960 Grid System - http://960.gs/ & 960 Fluid - http://www.designinfluences.com/ 5 | 6 | Licensed under GPL and MIT. 7 | */ 8 | 9 | 10 | /* Containers 11 | ----------------------------------------------------------------------------------------------------*/ 12 | .container_12 { 13 | width: 92%; 14 | margin-left: 4%; 15 | margin-right: 4%; 16 | } 17 | 18 | /* Grid >> Global 19 | ----------------------------------------------------------------------------------------------------*/ 20 | 21 | .grid_1, 22 | .grid_2, 23 | .grid_3, 24 | .grid_4, 25 | .grid_5, 26 | .grid_6, 27 | .grid_7, 28 | .grid_8, 29 | .grid_9, 30 | .grid_10, 31 | .grid_11, 32 | .grid_12 { 33 | display:inline; 34 | float: left; 35 | position: relative; 36 | margin-left: 1%; 37 | margin-right: 1%; 38 | } 39 | 40 | /* Grid >> Children (Alpha ~ First, Omega ~ Last) 41 | ----------------------------------------------------------------------------------------------------*/ 42 | 43 | .alpha { 44 | margin-left: 0; 45 | } 46 | 47 | .omega { 48 | margin-right: 0; 49 | } 50 | 51 | /* Grid >> 12 Columns 52 | ----------------------------------------------------------------------------------------------------*/ 53 | 54 | 55 | .container_12 .grid_1 { 56 | width:6.333%; 57 | } 58 | 59 | .container_12 .grid_2 { 60 | width:14.667%; 61 | } 62 | 63 | .container_12 .grid_3 { 64 | width:23.0%; 65 | } 66 | 67 | .container_12 .grid_4 { 68 | width:31.333%; 69 | } 70 | 71 | .container_12 .grid_5 { 72 | width:39.667%; 73 | } 74 | 75 | .container_12 .grid_6 { 76 | width:48.0%; 77 | } 78 | 79 | .container_12 .grid_7 { 80 | width:56.333%; 81 | } 82 | 83 | .container_12 .grid_8 { 84 | width:64.667%; 85 | } 86 | 87 | .container_12 .grid_9 { 88 | width:73.0%; 89 | } 90 | 91 | .container_12 .grid_10 { 92 | width:81.333%; 93 | } 94 | 95 | .container_12 .grid_11 { 96 | width:89.667%; 97 | } 98 | 99 | .container_12 .grid_12 { 100 | width:98.0%; 101 | } 102 | 103 | 104 | 105 | /* Prefix Extra Space >> 12 Columns 106 | ----------------------------------------------------------------------------------------------------*/ 107 | 108 | 109 | .container_12 .prefix_1 { 110 | padding-left:8.333%; 111 | } 112 | 113 | .container_12 .prefix_2 { 114 | padding-left:16.667%; 115 | } 116 | 117 | .container_12 .prefix_3 { 118 | padding-left:25.0%; 119 | } 120 | 121 | .container_12 .prefix_4 { 122 | padding-left:33.333%; 123 | } 124 | 125 | .container_12 .prefix_5 { 126 | padding-left:41.667%; 127 | } 128 | 129 | .container_12 .prefix_6 { 130 | padding-left:50.0%; 131 | } 132 | 133 | .container_12 .prefix_7 { 134 | padding-left:58.333%; 135 | } 136 | 137 | .container_12 .prefix_8 { 138 | padding-left:66.667%; 139 | } 140 | 141 | .container_12 .prefix_9 { 142 | padding-left:75.0%; 143 | } 144 | 145 | .container_12 .prefix_10 { 146 | padding-left:83.333%; 147 | } 148 | 149 | .container_12 .prefix_11 { 150 | padding-left:91.667%; 151 | } 152 | 153 | 154 | 155 | /* Suffix Extra Space >> 12 Columns 156 | ----------------------------------------------------------------------------------------------------*/ 157 | 158 | 159 | .container_12 .suffix_1 { 160 | padding-right:8.333%; 161 | } 162 | 163 | .container_12 .suffix_2 { 164 | padding-right:16.667%; 165 | } 166 | 167 | .container_12 .suffix_3 { 168 | padding-right:25.0%; 169 | } 170 | 171 | .container_12 .suffix_4 { 172 | padding-right:33.333%; 173 | } 174 | 175 | .container_12 .suffix_5 { 176 | padding-right:41.667%; 177 | } 178 | 179 | .container_12 .suffix_6 { 180 | padding-right:50.0%; 181 | } 182 | 183 | .container_12 .suffix_7 { 184 | padding-right:58.333%; 185 | } 186 | 187 | .container_12 .suffix_8 { 188 | padding-right:66.667%; 189 | } 190 | 191 | .container_12 .suffix_9 { 192 | padding-right:75.0%; 193 | } 194 | 195 | .container_12 .suffix_10 { 196 | padding-right:83.333%; 197 | } 198 | 199 | .container_12 .suffix_11 { 200 | padding-right:91.667%; 201 | } 202 | 203 | 204 | 205 | /* Push Space >> 12 Columns 206 | ----------------------------------------------------------------------------------------------------*/ 207 | 208 | 209 | .container_12 .push_1 { 210 | left:8.333%; 211 | } 212 | 213 | .container_12 .push_2 { 214 | left:16.667%; 215 | } 216 | 217 | .container_12 .push_3 { 218 | left:25.0%; 219 | } 220 | 221 | .container_12 .push_4 { 222 | left:33.333%; 223 | } 224 | 225 | .container_12 .push_5 { 226 | left:41.667%; 227 | } 228 | 229 | .container_12 .push_6 { 230 | left:50.0%; 231 | } 232 | 233 | .container_12 .push_7 { 234 | left:58.333%; 235 | } 236 | 237 | .container_12 .push_8 { 238 | left:66.667%; 239 | } 240 | 241 | .container_12 .push_9 { 242 | left:75.0%; 243 | } 244 | 245 | .container_12 .push_10 { 246 | left:83.333%; 247 | } 248 | 249 | .container_12 .push_11 { 250 | left:91.667%; 251 | } 252 | 253 | 254 | 255 | /* Pull Space >> 12 Columns 256 | ----------------------------------------------------------------------------------------------------*/ 257 | 258 | 259 | .container_12 .pull_1 { 260 | left:-8.333%; 261 | } 262 | 263 | .container_12 .pull_2 { 264 | left:-16.667%; 265 | } 266 | 267 | .container_12 .pull_3 { 268 | left:-25.0%; 269 | } 270 | 271 | .container_12 .pull_4 { 272 | left:-33.333%; 273 | } 274 | 275 | .container_12 .pull_5 { 276 | left:-41.667%; 277 | } 278 | 279 | .container_12 .pull_6 { 280 | left:-50.0%; 281 | } 282 | 283 | .container_12 .pull_7 { 284 | left:-58.333%; 285 | } 286 | 287 | .container_12 .pull_8 { 288 | left:-66.667%; 289 | } 290 | 291 | .container_12 .pull_9 { 292 | left:-75.0%; 293 | } 294 | 295 | .container_12 .pull_10 { 296 | left:-83.333%; 297 | } 298 | 299 | .container_12 .pull_11 { 300 | left:-91.667%; 301 | } 302 | 303 | 304 | 305 | 306 | /* Clear Floated Elements 307 | ----------------------------------------------------------------------------------------------------*/ 308 | 309 | /* http://sonspring.com/journal/clearing-floats */ 310 | 311 | .clear { 312 | clear: both; 313 | display: block; 314 | overflow: hidden; 315 | visibility: hidden; 316 | width: 0; 317 | height: 0; 318 | } 319 | 320 | /* http://perishablepress.com/press/2008/02/05/lessons-learned-concerning-the-clearfix-css-hack */ 321 | 322 | .clearfix:after { 323 | clear: both; 324 | content: ' '; 325 | display: block; 326 | font-size: 0; 327 | line-height: 0; 328 | visibility: hidden; 329 | width: 0; 330 | height: 0; 331 | } 332 | 333 | .clearfix { 334 | display: inline-block; 335 | } 336 | 337 | * html .clearfix { 338 | height: 1%; 339 | } 340 | 341 | .clearfix { 342 | display: block; 343 | } -------------------------------------------------------------------------------- /src/security-random.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-random -- Random numbers for nonce, secret keys, token generation 3 | -- Copyright (C) 2017, 2022, 2023, 2025 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | with Ada.Calendar; 8 | with Ada.Calendar.Conversions; 9 | with Util.Encoders.Base64; 10 | 11 | package body Security.Random is 12 | 13 | use Interfaces; 14 | 15 | -- ------------------------------ 16 | -- Initialize the random generator. 17 | -- ------------------------------ 18 | overriding 19 | procedure Initialize (Gen : in out Generator) is 20 | begin 21 | Gen.Rand.Reset; 22 | end Initialize; 23 | 24 | -- ------------------------------ 25 | -- Fill the array with pseudo-random numbers. 26 | -- ------------------------------ 27 | procedure Generate (Gen : in out Generator; 28 | Into : out Ada.Streams.Stream_Element_Array) is 29 | begin 30 | Gen.Rand.Generate (Into); 31 | end Generate; 32 | 33 | procedure Generate (Gen : in out Generator; 34 | Into : out String) is 35 | Buf : Ada.Streams.Stream_Element_Array (1 .. Into'Length); 36 | for Buf'Address use Into'Address; 37 | begin 38 | Gen.Rand.Generate (Buf); 39 | end Generate; 40 | 41 | -- ------------------------------ 42 | -- Generate a random sequence of bits and convert the result 43 | -- into a string in base64url. 44 | -- ------------------------------ 45 | function Generate (Gen : in out Generator'Class; 46 | Bits : in Positive) return String is 47 | use type Ada.Streams.Stream_Element_Offset; 48 | 49 | Rand_Count : constant Ada.Streams.Stream_Element_Offset 50 | := Ada.Streams.Stream_Element_Offset (4 * ((Bits + 31) / 32)); 51 | 52 | Rand : Ada.Streams.Stream_Element_Array (0 .. Rand_Count - 1); 53 | Buffer : Ada.Streams.Stream_Element_Array (0 .. Rand_Count * 3); 54 | Encoder : Util.Encoders.Base64.Encoder; 55 | Last : Ada.Streams.Stream_Element_Offset; 56 | Encoded : Ada.Streams.Stream_Element_Offset; 57 | begin 58 | -- Generate the random sequence. 59 | Gen.Generate (Rand); 60 | 61 | -- Encode the random stream in base64url and save it into the result string. 62 | Encoder.Set_URL_Mode (True); 63 | Encoder.Transform (Data => Rand, Into => Buffer, 64 | Last => Last, Encoded => Encoded); 65 | Encoder.Finish (Into => Buffer (Last + 1 .. Buffer'Last), 66 | Last => Last); 67 | while Character'Val (Buffer (Last)) = '=' loop 68 | Last := Last - 1; 69 | end loop; 70 | declare 71 | Result : String (1 .. Natural (Last + 1)); 72 | begin 73 | for I in 0 .. Last loop 74 | Result (Natural (I + 1)) := Character'Val (Buffer (I)); 75 | end loop; 76 | return Result; 77 | end; 78 | end Generate; 79 | 80 | -- ------------------------------ 81 | -- Generate a random sequence of bits, convert the result 82 | -- into a string in base64url and append it to the buffer. 83 | -- ------------------------------ 84 | procedure Generate (Gen : in out Generator'Class; 85 | Bits : in Positive; 86 | Into : in out Ada.Strings.Unbounded.Unbounded_String) is 87 | use type Ada.Streams.Stream_Element_Offset; 88 | 89 | Rand_Count : constant Ada.Streams.Stream_Element_Offset 90 | := Ada.Streams.Stream_Element_Offset (4 * ((Bits + 31) / 32)); 91 | 92 | Rand : Ada.Streams.Stream_Element_Array (0 .. Rand_Count - 1); 93 | Buffer : Ada.Streams.Stream_Element_Array (0 .. Rand_Count * 3); 94 | Encoder : Util.Encoders.Base64.Encoder; 95 | Last : Ada.Streams.Stream_Element_Offset; 96 | Encoded : Ada.Streams.Stream_Element_Offset; 97 | begin 98 | -- Generate the random sequence. 99 | Gen.Generate (Rand); 100 | 101 | -- Encode the random stream in base64url and save it into the result string. 102 | Encoder.Set_URL_Mode (True); 103 | Encoder.Transform (Data => Rand, Into => Buffer, 104 | Last => Last, Encoded => Encoded); 105 | Encoder.Finish (Into => Buffer (Last + 1 .. Buffer'Last), 106 | Last => Last); 107 | while Character'Val (Buffer (Last)) = '=' loop 108 | Last := Last - 1; 109 | end loop; 110 | for I in 0 .. Last loop 111 | Ada.Strings.Unbounded.Append (Into, Character'Val (Buffer (I))); 112 | end loop; 113 | end Generate; 114 | 115 | -- Protected type to allow using the random generator by several tasks. 116 | protected body Raw_Generator is 117 | 118 | procedure Generate (Into : out Ada.Streams.Stream_Element_Array) is 119 | use Ada.Streams; 120 | 121 | Size : constant Ada.Streams.Stream_Element_Offset := Into'Length / 4; 122 | Remain : constant Ada.Streams.Stream_Element_Offset := Into'Length mod 4; 123 | Value : Unsigned_32; 124 | begin 125 | -- Generate the random sequence (fill 32-bits at a time for each random call). 126 | for I in 0 .. Size - 1 loop 127 | Value := Id_Random.Random (Rand); 128 | Into (Into'First + 4 * I) := Stream_Element (Value and 16#0FF#); 129 | Into (Into'First + 4 * I + 1) := Stream_Element (Shift_Right (Value, 8) and 16#0FF#); 130 | Into (Into'First + 4 * I + 2) := Stream_Element (Shift_Right (Value, 16) and 16#0FF#); 131 | Into (Into'First + 4 * I + 3) := Stream_Element (Shift_Right (Value, 24) and 16#0FF#); 132 | end loop; 133 | 134 | -- Fill the remaining bytes. 135 | if Remain > 0 then 136 | Value := Id_Random.Random (Rand); 137 | for I in 0 .. Remain - 1 loop 138 | Into (Into'Last - I) := Stream_Element (Value and 16#0FF#); 139 | Value := Shift_Right (Value, 8); 140 | end loop; 141 | end if; 142 | end Generate; 143 | 144 | procedure Reset is 145 | Now : constant Ada.Calendar.Time := Ada.Calendar.Clock; 146 | Nsec : constant Unsigned_64 147 | := Unsigned_64 (Ada.Calendar.Conversions.To_Unix_Nano_Time (Now)); 148 | Low : constant Unsigned_32 := Unsigned_32 (Nsec and 16#0ffff_ffff#); 149 | High : constant Unsigned_32 := Unsigned_32 (Shift_Right (Nsec, 32)); 150 | begin 151 | Id_Random.Reset (Rand, Integer ((Low xor High) and 16#07fff_ffff#)); 152 | end Reset; 153 | 154 | end Raw_Generator; 155 | 156 | end Security.Random; 157 | -------------------------------------------------------------------------------- /regtests/src/security-oauth-clients-tests.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- Security-oauth-clients-tests - Unit tests for OAuth 3 | -- Copyright (C) 2013 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | with Ada.Strings.Fixed; 8 | with Util.Test_Caller; 9 | with Util.Measures; 10 | with Util.Strings.Sets; 11 | 12 | package body Security.OAuth.Clients.Tests is 13 | 14 | package Caller is new Util.Test_Caller (Test, "Security.OAuth.Clients"); 15 | 16 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite) is 17 | begin 18 | Caller.Add_Test (Suite, "Test Security.OAuth.Clients.Create_Nonce", 19 | Test_Create_Nonce'Access); 20 | Caller.Add_Test (Suite, "Test Security.OAuth.Clients.Get_State", 21 | Test_Get_State'Access); 22 | Caller.Add_Test (Suite, "Test Security.OAuth.Clients.Is_Valid_State", 23 | Test_Is_Valid_State'Access); 24 | Caller.Add_Test (Suite, "Test Security.OAuth.Clients.Get_Auth_Params", 25 | Test_Get_Auth_Params'Access); 26 | end Add_Tests; 27 | 28 | -- ------------------------------ 29 | -- Test Create_Nonce operation. 30 | -- ------------------------------ 31 | procedure Test_Create_Nonce (T : in out Test) is 32 | Nonces : Util.Strings.Sets.Set; 33 | begin 34 | for I in 1 .. 1_000 loop 35 | for I in 32 .. 734 loop 36 | declare 37 | S : constant String := Create_Nonce (I * 3); 38 | begin 39 | T.Assert (not Nonces.Contains (S), "Nonce was not unique: " & S); 40 | Nonces.Include (S); 41 | end; 42 | end loop; 43 | end loop; 44 | declare 45 | S : Util.Measures.Stamp; 46 | begin 47 | for I in 1 .. 1_000 loop 48 | declare 49 | Nonce : constant String := Create_Nonce (128); 50 | pragma Unreferenced (Nonce); 51 | begin 52 | null; 53 | end; 54 | end loop; 55 | Util.Measures.Report (S, "128 bits nonce generation", 1_000); 56 | end; 57 | end Test_Create_Nonce; 58 | 59 | -- ------------------------------ 60 | -- Test the Get_State operation. 61 | -- ------------------------------ 62 | procedure Test_Get_State (T : in out Test) is 63 | App : Application; 64 | Nonce : constant String := Create_Nonce (128); 65 | begin 66 | App.Set_Application_Identifier ("test"); 67 | Util.Tests.Assert_Equals (T, "test", App.Get_Application_Identifier, "Invalid application"); 68 | 69 | App.Set_Application_Secret ("my-secret"); 70 | App.Set_Application_Callback ("my-callback"); 71 | App.Set_Provider_URI ("http://my-provider"); 72 | 73 | declare 74 | State : constant String := App.Get_State (Nonce); 75 | begin 76 | T.Assert (State'Length > 25, "State is too small: " & State); 77 | T.Assert (Ada.Strings.Fixed.Index (State, Nonce) = 0, 78 | "The state must not contain the nonce"); 79 | 80 | -- Calling Get_State with the same nonce should produce the same result. 81 | Util.Tests.Assert_Equals (T, State, App.Get_State (Nonce), "Invalid state"); 82 | 83 | App.Set_Application_Secret ("second-secret"); 84 | declare 85 | State2 : constant String := App.Get_State (Nonce); 86 | begin 87 | T.Assert (State /= State2, 88 | "Changing the application key should produce a different state"); 89 | end; 90 | 91 | -- Restore the secret and change the callback. 92 | App.Set_Application_Secret ("my-secret"); 93 | App.Set_Application_Callback ("my-callback2"); 94 | declare 95 | State2 : constant String := App.Get_State (Nonce); 96 | begin 97 | T.Assert (State /= State2, 98 | "Changing the application callback should produce a different state"); 99 | end; 100 | 101 | -- Restore the callback and change the client Id. 102 | App.Set_Application_Callback ("my-callback"); 103 | App.Set_Application_Identifier ("test2"); 104 | declare 105 | State2 : constant String := App.Get_State (Nonce); 106 | begin 107 | T.Assert (State /= State2, 108 | "Changing the application identifier should produce a different state"); 109 | end; 110 | end; 111 | end Test_Get_State; 112 | 113 | -- ------------------------------ 114 | -- Test the Is_Valid_State operation. 115 | -- ------------------------------ 116 | procedure Test_Is_Valid_State (T : in out Test) is 117 | App : Application; 118 | begin 119 | App.Set_Application_Identifier ("test"); 120 | Util.Tests.Assert_Equals (T, "test", App.Get_Application_Identifier, "Invalid application"); 121 | 122 | App.Set_Application_Secret ("my-secret"); 123 | App.Set_Application_Callback ("my-callback"); 124 | App.Set_Provider_URI ("http://my-provider"); 125 | 126 | for I in 1 .. 100 loop 127 | declare 128 | Nonce : constant String := Create_Nonce (128); 129 | State : constant String := App.Get_State (Nonce); 130 | begin 131 | T.Assert (State'Length > 25, "State is too small: " & State); 132 | T.Assert (App.Is_Valid_State (Nonce, State), "Invalid state: " & State); 133 | T.Assert (not App.Is_Valid_State ("", State), "State was valid with invalid nonce"); 134 | T.Assert (not App.Is_Valid_State (State, State), "State must be invalid"); 135 | T.Assert (not App.Is_Valid_State (Nonce, State & "d"), "State must be invalid"); 136 | end; 137 | end loop; 138 | end Test_Is_Valid_State; 139 | 140 | -- Test the Get_Auth_Params operation. 141 | procedure Test_Get_Auth_Params (T : in out Test) is 142 | App : Application; 143 | begin 144 | App.Set_Application_Identifier ("test"); 145 | Util.Tests.Assert_Equals (T, "test", App.Get_Application_Identifier, "Invalid application"); 146 | 147 | App.Set_Application_Secret ("my-secret"); 148 | App.Set_Application_Callback ("my-callback"); 149 | App.Set_Provider_URI ("http://my-provider"); 150 | declare 151 | P : constant String := App.Get_Auth_Params ("the-state", "the-scope"); 152 | begin 153 | Util.Tests.Assert_Equals (T, "client_id=test&redirect_uri=my-callback&" 154 | & "scope=the-scope&state=the-state", P, 155 | "Invalid auth params"); 156 | end; 157 | end Test_Get_Auth_Params; 158 | 159 | end Security.OAuth.Clients.Tests; 160 | -------------------------------------------------------------------------------- /regtests/src/security-auth-tests.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-auth-tests - Tests for OpenID 3 | -- Copyright (C) 2009, 2010, 2011, 2012, 2013, 2015, 2019 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Ada.Strings.Unbounded; 9 | 10 | with Util.Http.Clients.Mockups; 11 | 12 | with Util.Test_Caller; 13 | with Ada.Text_IO; 14 | package body Security.Auth.Tests is 15 | 16 | package Caller is new Util.Test_Caller (Test, "Security.Openid"); 17 | procedure Setup (M : in out Manager; 18 | Name : in String); 19 | 20 | procedure Check_Discovery (T : in out Test; 21 | Name : in String; 22 | URI : in String); 23 | 24 | procedure Add_Tests (Suite : in Util.Tests.Access_Test_Suite) is 25 | begin 26 | Caller.Add_Test (Suite, "Test Security.OpenID.Discover", 27 | Test_Discovery'Access); 28 | Caller.Add_Test (Suite, "Test Security.OpenID.Verify_Signature", 29 | Test_Verify_Signature'Access); 30 | end Add_Tests; 31 | 32 | overriding 33 | function Get_Parameter (Params : in Test_Parameters; 34 | Name : in String) return String is 35 | begin 36 | if Params.Params.Contains (Name) then 37 | return Params.Params.Element (Name); 38 | else 39 | return ""; 40 | end if; 41 | end Get_Parameter; 42 | 43 | procedure Set_Parameter (Params : in out Test_Parameters; 44 | Name : in String; 45 | Value : in String) is 46 | begin 47 | Params.Params.Include (Name, Value); 48 | end Set_Parameter; 49 | 50 | procedure Setup (M : in out Manager; 51 | Name : in String) is 52 | Params : Test_Parameters; 53 | begin 54 | Params.Set_Parameter ("auth.provider.google", "openid"); 55 | Params.Set_Parameter ("auth.provider.openid", "openid"); 56 | Params.Set_Parameter ("openid.realm", "http://localhost/verify"); 57 | Params.Set_Parameter ("openid.callback_url", "http://localhost/openId"); 58 | M.Initialize (Params, Name); 59 | end Setup; 60 | 61 | procedure Check_Discovery (T : in out Test; 62 | Name : in String; 63 | URI : in String) is 64 | pragma Unreferenced (URI, T); 65 | 66 | M : Manager; 67 | Dir : constant String := "regtests/files/discover/"; 68 | Path : constant String := Util.Tests.Get_Path (Dir); 69 | Result : End_Point; 70 | begin 71 | Setup (M, "openid"); 72 | Util.Http.Clients.Mockups.Register; 73 | Util.Http.Clients.Mockups.Set_File (Path & Name & ".xrds"); 74 | M.Discover (Name => Name, 75 | Result => Result); 76 | Ada.Text_IO.Put_Line ("Result: " & To_String (Result)); 77 | end Check_Discovery; 78 | 79 | -- ------------------------------ 80 | -- Test Yadis discovery using static files 81 | -- ------------------------------ 82 | procedure Test_Discovery (T : in out Test) is 83 | begin 84 | Check_Discovery (T, "google", "https://www.google.com/accounts/o8/ud"); 85 | Check_Discovery (T, "yahoo", "https://open.login.yahooapis.com/openid/op/auth"); 86 | Check_Discovery (T, "claimid", ""); 87 | Check_Discovery (T, "livejournal", ""); 88 | Check_Discovery (T, "myopenid", ""); 89 | Check_Discovery (T, "myspace", ""); 90 | Check_Discovery (T, "orange", ""); 91 | Check_Discovery (T, "verisign", ""); 92 | Check_Discovery (T, "steamcommunity", ""); 93 | end Test_Discovery; 94 | 95 | -- ------------------------------ 96 | -- Test the OpenID verify signature process 97 | -- ------------------------------ 98 | procedure Test_Verify_Signature (T : in out Test) is 99 | Assoc : Association; 100 | Req : Test_Parameters; 101 | M : Manager; 102 | Result : Authentication; 103 | begin 104 | Setup (M, "openid"); 105 | 106 | pragma Style_Checks ("-mr"); 107 | 108 | -- Below is a part of the authentication process on Google OpenId. 109 | -- In theory, you cannot use the following information to authenticate again... 110 | Assoc.Session_Type := To_Unbounded_String ("no-encryption"); 111 | Assoc.Assoc_Type := To_Unbounded_String ("HMAC-SHA1"); 112 | Assoc.Assoc_Handle := To_Unbounded_String ("AOQobUdTfNDRSgJLi_0mQQnCCstOsefQadOiW9LNSp4JFO815iHCHsRk"); 113 | Assoc.Mac_Key := To_Unbounded_String ("NGFpR6vWfe7O8YIhhnXQMjL0goI="); 114 | 115 | Req.Set_Parameter ("openid.ns", "http://specs.openid.net/auth/2.0"); 116 | Req.Set_Parameter ("openid.mode", "id_res"); 117 | Req.Set_Parameter ("openid.op_endpoint", "https://www.google.com/accounts/o8/ud"); 118 | Req.Set_Parameter ("openid.response_nonce", "2011-04-26T20:08:22ZJ_neiVqR0e1wZw"); 119 | Req.Set_Parameter ("openid.return_to", "http://localhost/openId"); 120 | Req.Set_Parameter ("openid.assoc_handle", "AOQobUdTfNDRSgJLi_0mQQnCCstOsefQadOiW9LNSp4JFO815iHCHsRk"); 121 | Req.Set_Parameter ("openid.signed", "op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle,ns.ext1,ext1.mode,ext1.type.firstname,ext1.value.firstname,ext1.type.email,ext1.value.email,ext1.type.language,ext1.value.language,ext1.type.lastname,ext1.value.lastname"); 122 | Req.Set_Parameter ("openid.sig", "pV8cmScjrmgKvFn2F6Wxh/qBiIE="); 123 | Req.Set_Parameter ("openid.identity", "https://www.google.com/accounts/o8/id?id=AItOawm4O6C695XlWrS7MUWC-_V_R2zC-Ol993E"); 124 | Req.Set_Parameter ("openid.claimed_id", "https://www.google.com/accounts/o8/id?id=AItOawm4O6C695XlWrS7MUWC-_V_R2zC-Ol993E"); 125 | Req.Set_Parameter ("openid.ns.ext1", "http://openid.net/srv/ax/1.0"); 126 | Req.Set_Parameter ("openid.ext1.mode", "fetch_response"); 127 | Req.Set_Parameter ("openid.ext1.type.firstname", "http://axschema.org/namePerson/first"); 128 | Req.Set_Parameter ("openid.ext1.value.firstname", "Stephane"); 129 | Req.Set_Parameter ("openid.ext1.type.email", "http://axschema.org/contact/email"); 130 | Req.Set_Parameter ("openid.ext1.value.email", "stephane.carrez@gmail.com"); 131 | Req.Set_Parameter ("openid.ext1.type.language", "http://axschema.org/pref/language"); 132 | Req.Set_Parameter ("openid.ext1.value.language", "fr"); 133 | Req.Set_Parameter ("openid.ext1.type.lastname", "http://axschema.org/namePerson/last"); 134 | Req.Set_Parameter ("openid.ext1.value.lastname", "Carrez"); 135 | 136 | M.Verify (Assoc, Req, Result); 137 | 138 | -- If the verification is succeeds, the signature is correct, we should be authenticated. 139 | T.Assert (Get_Status (Result) = AUTHENTICATED, "Authentication status is not authenticated"); 140 | Util.Tests.Assert_Equals (T, "stephane.carrez@gmail.com", Get_Email (Result), "Invalid email"); 141 | end Test_Verify_Signature; 142 | 143 | end Security.Auth.Tests; 144 | -------------------------------------------------------------------------------- /src/security-oauth-jwt.adb: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------- 2 | -- security-oauth-jwt -- OAuth Java Web Token 3 | -- Copyright (C) 2013, 2017 Stephane Carrez 4 | -- Written by Stephane Carrez (Stephane.Carrez@gmail.com) 5 | -- SPDX-License-Identifier: Apache-2.0 6 | ----------------------------------------------------------------------- 7 | 8 | with Ada.Calendar.Conversions; 9 | with Interfaces.C; 10 | 11 | with Util.Encoders; 12 | with Util.Strings; 13 | with Util.Serialize.IO; 14 | with Util.Properties.JSON; 15 | with Util.Log.Loggers; 16 | package body Security.OAuth.JWT is 17 | 18 | Log : constant Util.Log.Loggers.Logger := Util.Log.Loggers.Create ("Security.OAuth.JWT"); 19 | 20 | function Get_Time (From : in Util.Properties.Manager; 21 | Name : in String) return Ada.Calendar.Time; 22 | 23 | -- Decode the part using base64url and parse the JSON content into the property manager. 24 | procedure Decode_Part (Into : in out Util.Properties.Manager; 25 | Name : in String; 26 | Data : in String); 27 | 28 | function Get_Time (From : in Util.Properties.Manager; 29 | Name : in String) return Ada.Calendar.Time is 30 | Value : constant String := From.Get (Name); 31 | begin 32 | return Ada.Calendar.Conversions.To_Ada_Time (Interfaces.C.long'Value (Value)); 33 | end Get_Time; 34 | 35 | -- ------------------------------ 36 | -- Get the issuer claim from the token (the "iss" claim). 37 | -- ------------------------------ 38 | function Get_Issuer (From : in Token) return String is 39 | begin 40 | return From.Claims.Get ("iss"); 41 | end Get_Issuer; 42 | 43 | -- ------------------------------ 44 | -- Get the subject claim from the token (the "sub" claim). 45 | -- ------------------------------ 46 | function Get_Subject (From : in Token) return String is 47 | begin 48 | return From.Claims.Get ("sub"); 49 | end Get_Subject; 50 | 51 | -- ------------------------------ 52 | -- Get the audience claim from the token (the "aud" claim). 53 | -- ------------------------------ 54 | function Get_Audience (From : in Token) return String is 55 | begin 56 | return From.Claims.Get ("aud"); 57 | end Get_Audience; 58 | 59 | -- ------------------------------ 60 | -- Get the expiration claim from the token (the "exp" claim). 61 | -- ------------------------------ 62 | function Get_Expiration (From : in Token) return Ada.Calendar.Time is 63 | begin 64 | return Get_Time (From.Claims, "exp"); 65 | end Get_Expiration; 66 | 67 | -- ------------------------------ 68 | -- Get the not before claim from the token (the "nbf" claim). 69 | -- ------------------------------ 70 | function Get_Not_Before (From : in Token) return Ada.Calendar.Time is 71 | begin 72 | return Get_Time (From.Claims, "nbf"); 73 | end Get_Not_Before; 74 | 75 | -- ------------------------------ 76 | -- Get the issued at claim from the token (the "iat" claim). 77 | -- ------------------------------ 78 | function Get_Issued_At (From : in Token) return Ada.Calendar.Time is 79 | begin 80 | return Get_Time (From.Claims, "iat"); 81 | end Get_Issued_At; 82 | 83 | -- ------------------------------ 84 | -- Get the authentication time claim from the token (the "auth_time" claim). 85 | -- ------------------------------ 86 | function Get_Authentication_Time (From : in Token) return Ada.Calendar.Time is 87 | begin 88 | return Get_Time (From.Claims, "auth_time"); 89 | end Get_Authentication_Time; 90 | 91 | -- ------------------------------ 92 | -- Get the JWT ID claim from the token (the "jti" claim). 93 | -- ------------------------------ 94 | function Get_JWT_ID (From : in Token) return String is 95 | begin 96 | return From.Claims.Get ("jti"); 97 | end Get_JWT_ID; 98 | 99 | -- ------------------------------ 100 | -- Get the authorized clients claim from the token (the "azp" claim). 101 | -- ------------------------------ 102 | function Get_Authorized_Presenters (From : in Token) return String is 103 | begin 104 | return From.Claims.Get ("azp"); 105 | end Get_Authorized_Presenters; 106 | 107 | -- ------------------------------ 108 | -- Get the claim with the given name from the token. 109 | -- ------------------------------ 110 | function Get_Claim (From : in Token; 111 | Name : in String; 112 | Default : in String := "") return String is 113 | begin 114 | return From.Claims.Get (Name, Default); 115 | end Get_Claim; 116 | 117 | -- ------------------------------ 118 | -- Decode the part using base64url and parse the JSON content into the property manager. 119 | -- ------------------------------ 120 | procedure Decode_Part (Into : in out Util.Properties.Manager; 121 | Name : in String; 122 | Data : in String) is 123 | Decoder : constant Util.Encoders.Decoder := Util.Encoders.Create (Util.Encoders.BASE_64_URL); 124 | Content : constant String := Decoder.Decode (Data); 125 | begin 126 | Log.Debug ("Decoding {0}: {1}", Name, Content); 127 | 128 | Util.Properties.JSON.Parse_JSON (Into, Content); 129 | end Decode_Part; 130 | 131 | -- ------------------------------ 132 | -- Decode a string representing an encoded JWT token according to the JWT specification: 133 | -- 134 | -- Section 7. Rules for Creating and Validating a JWT 135 | -- 136 | -- The JWT token is composed of 3 parts encoded in Base64url and separated by '.' . 137 | -- The first part represents the header, the second part the claims and the last part 138 | -- the signature. The Decode operation splits the parts, decodes them, 139 | -- parses the JSON content represented by the header and the claims. 140 | -- The Decode operation does not verify the signature (yet!). 141 | -- 142 | -- Return the decoded token or raise an exception. 143 | -- ------------------------------ 144 | function Decode (Content : in String) return Token is 145 | Pos1 : constant Natural := Util.Strings.Index (Content, '.'); 146 | Pos2 : Natural; 147 | Result : Token; 148 | begin 149 | if Pos1 = 0 then 150 | Log.Error ("Invalid JWT token: missing '.' separator. JWT: {0}", Content); 151 | raise Invalid_Token with "Missing header separator"; 152 | end if; 153 | Pos2 := Util.Strings.Index (Content, '.', Pos1 + 1); 154 | if Pos2 = 0 then 155 | Log.Error ("Invalid JWT token: missing second '.' separator. JWT: {0}", Content); 156 | raise Invalid_Token with "Missing signature separator"; 157 | end if; 158 | Decode_Part (Result.Header, "header", Content (Content'First .. Pos1 - 1)); 159 | Decode_Part (Result.Claims, "claims", Content (Pos1 + 1 .. Pos2 - 1)); 160 | return Result; 161 | 162 | exception 163 | when Util.Serialize.IO.Parse_Error => 164 | raise Invalid_Token with "Invalid JSON content"; 165 | end Decode; 166 | 167 | end Security.OAuth.JWT; 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ada Security Library 2 | 3 | [![Alire](https://img.shields.io/endpoint?url=https://alire.ada.dev/badges/security.json)](https://alire.ada.dev/crates/security) 4 | [![Ada 2012](https://img.shields.io/badge/2012-inside-green?logo=ada&logoColor=white&logoSize=auto)](https://adaic.org/ada-resources/standards/ada12) 5 | [![Build Status](https://img.shields.io/endpoint?url=https://porion.vacs.fr/porion/api/v1/projects/ada-security/badges/build.json)](https://porion.vacs.fr/porion/projects/view/ada-security/summary) 6 | [![Test Status](https://img.shields.io/endpoint?url=https://porion.vacs.fr/porion/api/v1/projects/ada-security/badges/tests.json)](https://porion.vacs.fr/porion/projects/view/ada-securit/xunits) 7 | [![Coverage](https://img.shields.io/endpoint?url=https://porion.vacs.fr/porion/api/v1/projects/ada-security/badges/coverage.json)](https://porion.vacs.fr/porion/projects/view/ada-security/summary) 8 | [![Documentation Status](https://readthedocs.org/projects/ada-security/badge/?version=latest)](https://ada-security.readthedocs.io/en/latest/?badge=latest) 9 | [![Download](https://img.shields.io/badge/download-1.5.1-brightgreen.svg)](http://download.vacs.fr/ada-security/ada-security-1.5.1.tar.gz) 10 | [![License](https://img.shields.io/badge/license-APACHE2-blue.svg)](LICENSE) 11 | [![GitLab](https://img.shields.io/badge/repo-GitLab-6C488A.svg)](https://gitlab.com/stcarrez/ada-security) 12 | [![Commits](https://img.shields.io/github/commits-since/stcarrez/ada-security/1.5.1.svg)](Commits) 13 | 14 | Ada Security provides a security framework which allows applications to define 15 | and enforce security policies. This framework allows users to authenticate by using 16 | [OpenID Authentication 2.0](https://openid.net/specs/openid-authentication-2_0.html) 17 | as well as [OAuth 2.0](https://oauth.net/2/) protocol. 18 | It allows a web application to integrate easily with Yahoo!, Gitlab, Github, Facebook and 19 | Google+ authentication systems. 20 | The Ada05 library includes: 21 | 22 | * An OpenID client authentication, 23 | * An OAuth 2.0 client authentication, 24 | * An OpenID Connect authentication framework, 25 | * An OAuth 2.0 server authentication framework, 26 | * A policy based security framework to protect the resources 27 | 28 | ![Ada Security Overview](https://github.com/stcarrez/ada-security/wiki/images/AdaSecurity.jpg) 29 | 30 | The Ada Security library is used by the 31 | [Ada Web Application](https://gitlab.com/stcarrez/ada-awa) 32 | and the [OpenAPI Ada library](https://github.com/stcarrez/swagger-ada) 33 | to provide authentication and access control to users within the web applications. 34 | 35 | ## Version 1.5.2 - Under development 36 | - Fix #11: Ada.Calendar.Conversions.To_Struct_Timespec is deprecated 37 | 38 | ## Version 1.5.1 - Aug 2024 39 | - Cleanup build environment to drop configure 40 | 41 | [List all versions](https://gitlab.com/stcarrez/ada-security/blob/master/NEWS.md) 42 | 43 | ## Using with Alire 44 | 45 | If you are using [Alire](https://alire.ada.dev/) in your project, run the following command 46 | within your [Alire](https://alire.ada.dev/) project to use the library: 47 | 48 | ``` 49 | alr with security 50 | ``` 51 | 52 | ## Using without Alire 53 | 54 | If you don't have [Alire](https://alire.ada.dev/) or want to build and install the library 55 | on a specific place, run a `setup` command to configure the build as well as installation 56 | directory. 57 | For a detailed description on how you can configure, build and install the library 58 | refer to the [Installation](https://ada-security.readthedocs.io/en/latest/Installation/) guide. 59 | Otherwise, you can easily configure and build the library with the steps described below. 60 | 61 | The `HAVE_ALIRE` configuration allows you to disable the build with [Alire](https://alire.ada.dev/): 62 | 63 | ``` 64 | make setup BUILD=debug PREFIX=/build/install HAVE_ALIRE=no 65 | ``` 66 | 67 | Since this build method does not verify that all dependencies are met, make sure that you 68 | have already built and install the following components and they are available to `gprbuild` 69 | through `ADA_PROJECT_PATH` if needed: 70 | 71 | * [Ada Utility Library](https://gitlab.com/stcarrez/ada-util/) 72 | 73 | Then build, run the unit tests and install by using: 74 | 75 | ``` 76 | make 77 | make test 78 | make install 79 | ``` 80 | 81 | To use the installed libraries, make sure your `ADA_PROJECT_PATH` contains the directory 82 | where you installed the libraries (configured by the `PREFIX=` option in the setup phase). 83 | The installed GNAT projects are the same as those used when using [Alire](https://alire.ada.dev/). 84 | 85 | 86 | ## Running the tests 87 | 88 | The unit tests are built and executed with: 89 | 90 | ``` 91 | make test 92 | ``` 93 | 94 | ## Samples 95 | 96 | The package provides a simple AWS server that illustrates the OpenID and OpenConnect 97 | authentication. Because OAuth2 relies exclusively on HTTPS, you must use an AWS 98 | server that has the SSL support. Build it as follows: 99 | 100 | ``` 101 | cd samples 102 | alr build 103 | ``` 104 | 105 | Before launching the demo server, you must update the 'samples.properties' file 106 | and change the lines that contain PUT-HERE-YOUR-FACEBOOK-xxx with your client ID 107 | and client secrets. This change 108 | is required by the OAuth and OpenID Connect framework only. 109 | Then, run the server: 110 | ``` 111 | bin/auth_demo 112 | ``` 113 | and redirect your browser to: 114 | ``` 115 | http://localhost:8080/atlas/login.html 116 | ``` 117 | # Documentation 118 | 119 | The Ada Security sources as well as a wiki documentation is provided on: 120 | 121 | - [Ada Security Programmer's Guide](https://ada-security.readthedocs.io/en/latest/) 122 | - [Overview](https://gitlab.com/stcarrez/ada-security/wiki) 123 | - [Security Overview](https://gitlab.com/stcarrez/ada-security/wiki/Security) 124 | 125 | 126 | # Other Documentation 127 | 128 | The OAuth literature is quite complete on the Internet and there are several good tutorials and 129 | documentation. 130 | - [Facebook Login](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow) 131 | - [Using OAuth 2.0 to Access Google APIs](https://developers.google.com/identity/protocols/OAuth2) 132 | - [Yahoo OAuth 2.0 Guide](https://developer.yahoo.com/oauth2/guide/) 133 | - [Salesforce OAuth 2.0 Guide](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_understanding_authentication.htm) 134 | (this is a good guide if you want to learn) 135 | 136 | # References 137 | 138 | - [RFC 6749: The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749) 139 | - [RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750) 140 | - [RFC 6819: OAuth 2.0 Threat Model and Security Considerations](https://tools.ietf.org/html/rfc6819) 141 | - [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) 142 | 143 | # Sites Using Ada Security 144 | 145 | * [Java 2 Ada](https://blog.vacs.fr/) 146 | * [Ada France](https://www.ada-france.org/adafr/index.html) 147 | * [Atlas](https://demo.vacs.fr/atlas/index.html) 148 | * [Jason Project Manager](https://vdo.vacs.fr/vdo/index.html) 149 | * [Porion Build Manager](https://porion.vacs.fr/porion/index.html) 150 | --------------------------------------------------------------------------------