{"id":10592,"date":"2026-10-05T18:54:58","date_gmt":"2026-10-05T21:54:58","guid":{"rendered":"https:\/\/www.fernandoquadro.com.br\/html\/?p=10592"},"modified":"2026-10-05T18:54:58","modified_gmt":"2026-10-05T21:54:58","slug":"geonode-openid-connect-autenticacao-corporativa-e-sincronizacao-de-grupos-na-pratica","status":"publish","type":"post","link":"https:\/\/www.fernandoquadro.com.br\/html\/2026\/10\/05\/geonode-openid-connect-autenticacao-corporativa-e-sincronizacao-de-grupos-na-pratica\/","title":{"rendered":"GeoNode + OpenID Connect: autentica\u00e7\u00e3o corporativa e sincroniza\u00e7\u00e3o de grupos na pr\u00e1tica"},"content":{"rendered":"<p>Quando uma infraestrutura geoespacial come\u00e7a a ser utilizada por diferentes equipes de uma organiza\u00e7\u00e3o, chega um momento em que manter usu\u00e1rios e senhas diretamente no GeoNode deixa de ser uma boa estrat\u00e9gia.<\/p>\n<p>O usu\u00e1rio j\u00e1 possui uma identidade corporativa. Ele j\u00e1 entra em outros sistemas com a mesma conta. Em muitos casos, seus grupos e perfis tamb\u00e9m j\u00e1 s\u00e3o gerenciados centralmente.<\/p>\n<p>Nesse cen\u00e1rio, uma alternativa interessante \u00e9 integrar o GeoNode a um provedor de identidade utilizando OpenID Connect (OIDC).<\/p>\n<p>O objetivo deste artigo \u00e9 mostrar um exemplo gen\u00e9rico de integra\u00e7\u00e3o de um GeoNode 5 com um provedor OIDC, incluindo n\u00e3o apenas o login, mas tamb\u00e9m a utiliza\u00e7\u00e3o das claims retornadas pelo provedor para associar automaticamente o usu\u00e1rio aos grupos do GeoNode.<\/p>\n<p>A arquitetura \u00e9 aproximadamente esta:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode.png\" alt=\"\" width=\"1672\" height=\"327\" class=\"aligncenter size-full wp-image-10593\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode.png 1672w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode-300x59.png 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode-1024x200.png 1024w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode-768x150.png 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode-1536x300.png 1536w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode-600x117.png 600w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode-945x185.png 945w\" sizes=\"auto, (max-width: 1672px) 100vw, 1672px\" \/><\/p>\n<p>O detalhe interessante \u00e9 que o GeoNode n\u00e3o precisa saber como o Identity Provider (IdP) autenticou o usu\u00e1rio. Por tr\u00e1s do IdP pode existir LDAP, Active Directory, outro diret\u00f3rio corporativo ou at\u00e9 outro servi\u00e7o de identidade. Para o GeoNode, isso \u00e9 transparente.<\/p>\n<p><strong>OIDC, OAuth 2.0 e autentica\u00e7\u00e3o<\/strong><\/p>\n<p>\u00c9 comum OAuth 2.0 e OpenID Connect aparecerem juntos, mas eles n\u00e3o representam exatamente a mesma coisa. <\/p>\n<ul>\n<li>OAuth 2.0 foi criado principalmente para <strong>autoriza\u00e7\u00e3o<\/strong>.<\/li>\n<li>OpenID Connect acrescenta uma camada de <strong>identidade e autentica\u00e7\u00e3o<\/strong> sobre o OAuth 2.0.<\/li>\n<\/ul>\n<p>\u00c9 por isso que em uma integra\u00e7\u00e3o OIDC normalmente aparecem informa\u00e7\u00f5es como: authorization_endpoint, token_endpoint, userinfo_endpoint, jwks_uri, issuer. Al\u00e9m de scopes como: openid, profile e email.<\/p>\n<p>Para aplica\u00e7\u00f5es corporativas, frequentemente aparecem tamb\u00e9m scopes ou claims relacionadas a grupos e roles.<\/p>\n<p><strong>Ambiente utilizado neste exemplo<\/strong><\/p>\n<p>Este artigo considera uma instala\u00e7\u00e3o do GeoNode 5 baseada no geonode-project e executada com Docker Compose. <\/p>\n<p>Os comandos docker compose devem ser executados no diret\u00f3rio raiz do projeto, onde est\u00e1 o arquivo docker-compose.yml. <\/p>\n<p>Nos exemplos abaixo, utilizarei geonode_project como nome do pacote Django. Caso seu projeto utilize outro nome, substitua geonode_project pelo nome correspondente.<\/p>\n<p><strong><\/p>\n<h2>1. O Discovery Document<\/h2>\n<p><\/strong><\/p>\n<p>Uma das primeiras informa\u00e7\u00f5es que devemos solicitar \u00e0 equipe respons\u00e1vel pelo Identity Provider \u00e9 a URL de discovery do OIDC. Normalmente ela segue este padr\u00e3o:<\/p>\n<p><strong>https:\/\/idp.exemplo.org\/.well-known\/openid-configuration <\/strong> ou <strong>https:\/\/idp.exemplo.org\/security\/.well-known\/openid-configuration<\/strong><\/p>\n<p>Ao acessar essa URL, recebemos um JSON semelhante a:<\/p>\n<pre>\r\n{\r\n  \"issuer\": \"https:\/\/idp.exemplo.org\/\",\r\n  \"authorization_endpoint\": \"https:\/\/idp.exemplo.org\/connect\/authorize\",\r\n  \"token_endpoint\": \"https:\/\/idp.exemplo.org\/connect\/token\",\r\n  \"userinfo_endpoint\": \"https:\/\/idp.exemplo.org\/connect\/userinfo\",\r\n  \"jwks_uri\": \"https:\/\/idp.exemplo.org\/.well-known\/jwks\",\r\n  \"scopes_supported\": [\r\n    \"openid\",\r\n    \"profile\",\r\n    \"email\",\r\n    \"roles\"\r\n  ]\r\n}\r\n<\/pre>\n<p>Esse documento funciona praticamente como um mapa da integra\u00e7\u00e3o.<\/p>\n<p><strong><\/p>\n<h2>2. Registrar o GeoNode como client<\/h2>\n<p><\/strong><\/p>\n<p>No lado do Identity Provider, o GeoNode precisa ser registrado como uma aplica\u00e7\u00e3o\/client. De maneira simplificada, teremos algo como:<\/p>\n<pre>\r\nClient ID:\r\ngeonode\r\n\r\nClient Secret:\r\n**************\r\n\r\nRedirect URI:\r\nhttps:\/\/geo.exemplo.org\/account\/oidc\/corp-idp\/login\/callback\/\r\n<\/pre>\n<p>A URL de callback deve coincidir exatamente com a rota exposta pela vers\u00e3o do GeoNode\/django-allauth utilizada. Confirme a URL do seu ambiente antes de registr\u00e1-la no Identity Provider.<\/p>\n<p>O nome corp-idp neste artigo \u00e9 apenas um exemplo. Esse identificador ser\u00e1 utilizado posteriormente pelo django-allauth para identificar o provedor. Um cuidado importante: o Client Secret n\u00e3o deve ficar diretamente no c\u00f3digo-fonte nem ser enviado ao Git. O ideal \u00e9 mant\u00ea-lo em vari\u00e1vel de ambiente:<\/p>\n<pre>\r\nOIDC_CLIENT_ID=geonode\r\nOIDC_CLIENT_SECRET=xxxxxxxxxxxxxxxx\r\nOIDC_DISCOVERY_URL=https:\/\/idp.exemplo.org\/.well-known\/openid-configuration\r\n<\/pre>\n<p><strong><\/p>\n<h2>3. Criando settings espec\u00edficos<\/h2>\n<p><\/strong><\/p>\n<p>Uma estrat\u00e9gia que considero interessante \u00e9 n\u00e3o alterar diretamente o settings.py original do GeoNode. Podemos criar, por exemplo: <strong>src\/geonode_project\/corporate_settings.py<\/strong> com o seguinte conte\u00fado:<\/p>\n<pre>\r\nimport os\r\n\r\nfrom .settings import *\r\n\r\n\r\nOIDC_CLIENT_ID = os.getenv(\r\n    \"OIDC_CLIENT_ID\",\r\n    \"geonode\",\r\n)\r\n\r\nOIDC_CLIENT_SECRET = os.getenv(\r\n    \"OIDC_CLIENT_SECRET\",\r\n    \"\",\r\n)\r\n\r\nOIDC_DISCOVERY_URL = os.getenv(\r\n    \"OIDC_DISCOVERY_URL\",\r\n    \"\",\r\n)\r\n<\/pre>\n<p>E ent\u00e3o definir no ambiente:<\/p>\n<pre>\r\nDJANGO_SETTINGS_MODULE=geonode_project.corporate_settings\r\n<\/pre>\n<p>Isso facilita bastante manuten\u00e7\u00e3o, deploy e rollback.<\/p>\n<p><strong><\/p>\n<h2>4. Habilitando o provider OpenID Connect<\/h2>\n<p><\/strong><\/p>\n<p>O GeoNode utiliza django-allauth para parte da integra\u00e7\u00e3o com contas sociais\/federadas. O c\u00f3digo atual do projeto continua trazendo um <strong><em>GenericOpenIDConnectAdapter<\/em><\/strong>e mecanismos pr\u00f3prios para extrair atributos e sincronizar grupos. Podemos registrar o provider desta forma:<\/p>\n<pre>\r\nOIDC_PROVIDER_APP = (\r\n    \"allauth.socialaccount.providers.openid_connect\"\r\n)\r\n\r\nif OIDC_PROVIDER_APP not in INSTALLED_APPS:\r\n    INSTALLED_APPS += (OIDC_PROVIDER_APP,)\r\n\r\n\r\nSOCIALACCOUNT_PROVIDERS = dict(\r\n    SOCIALACCOUNT_PROVIDERS\r\n)\r\n\r\nSOCIALACCOUNT_PROVIDERS[\"openid_connect\"] = {\r\n    \"OAUTH_PKCE_ENABLED\": True,\r\n\r\n    \"SCOPE\": [\r\n        \"openid\",\r\n        \"profile\",\r\n        \"email\",\r\n        \"roles\",\r\n    ],\r\n\r\n    \"APPS\": [\r\n        {\r\n            \"provider_id\": \"corp-idp\",\r\n\r\n            \"name\": \"Login Corporativo\",\r\n\r\n            \"client_id\": OIDC_CLIENT_ID,\r\n\r\n            \"secret\": OIDC_CLIENT_SECRET,\r\n\r\n            \"settings\": {\r\n                \"server_url\": OIDC_DISCOVERY_URL,\r\n            },\r\n        }\r\n    ],\r\n}\r\n<\/pre>\n<p>O scope <strong>roles <\/strong>utilizado neste exemplo n\u00e3o \u00e9 obrigat\u00f3rio no padr\u00e3o OIDC. Os scopes e claims dispon\u00edveis dependem do Identity Provider. Alguns provedores retornam grupos\/roles com outros scopes ou apenas pelo endpoint userinfo.<\/p>\n<p>Tamb\u00e9m podemos permitir cria\u00e7\u00e3o autom\u00e1tica da conta local:<\/p>\n<pre>\r\nSOCIALACCOUNT_AUTO_SIGNUP = True\r\n<\/pre>\n<p>Durante homologa\u00e7\u00e3o, particularmente prefiro manter o login local dispon\u00edvel:<\/p>\n<pre>\r\nSOCIALACCOUNT_ONLY = False\r\n<\/pre>\n<p>Assim, caso algo d\u00ea errado com o IdP, ainda existe uma forma administrativa de entrar no GeoNode.<\/p>\n<p><strong><\/p>\n<h2>5. Primeiro teste: autentica\u00e7\u00e3o apenas<\/h2>\n<p><\/strong><\/p>\n<p>Neste momento eu ainda n\u00e3o me preocuparia com grupos. O primeiro objetivo \u00e9 provar apenas este fluxo:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode_2.png\" alt=\"\" width=\"1672\" height=\"472\" class=\"aligncenter size-full wp-image-10598\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode_2.png 1672w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode_2-300x85.png 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode_2-1024x289.png 1024w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode_2-768x217.png 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode_2-1536x434.png 1536w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode_2-600x169.png 600w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Autenticacao-GeoNode_2-945x267.png 945w\" sizes=\"auto, (max-width: 1672px) 100vw, 1672px\" \/><\/p>\n<p>Depois de reiniciar a aplica\u00e7\u00e3o:<\/p>\n<pre>\r\ndocker compose up -d --build django celery\r\n<\/pre>\n<p>Valide:<\/p>\n<pre>\r\ndocker compose exec django python manage.py check\r\n<\/pre>\n<p>Podemos tamb\u00e9m acompanhar o log:<\/p>\n<pre>\r\ndocker compose logs -f --tail=200 django\r\n<\/pre>\n<p>Ao clicar em Login Corporativo, devemos ser redirecionados ao IdP e, ap\u00f3s a autentica\u00e7\u00e3o, voltar para algo semelhante a: <strong>\/account\/oidc\/corp-idp\/login\/callback\/<\/strong><\/p>\n<p>Se o usu\u00e1rio for criado e entrar no GeoNode, a primeira etapa est\u00e1 conclu\u00edda.<\/p>\n<p><strong><\/p>\n<h2>6. O que o Identity Provider enviou?<\/h2>\n<p><\/strong><\/p>\n<p>Depois da autentica\u00e7\u00e3o, uma das etapas mais importantes \u00e9 verificar o conte\u00fado recebido. Um conjunto de claims pode ser parecido com:<\/p>\n<pre>\r\n{\r\n  \"sub\": \"5d823759-3dc0-4414-a936-2ef659829321\",\r\n\r\n  \"name\": \"Fernando Quadro\",\r\n\r\n  \"preferred_username\": \"fernando.quadro\",\r\n\r\n  \"email\": \"fernando.quadro@exemplo.org\",\r\n\r\n  \"given_name\": \"Fernando\",\r\n\r\n  \"family_name\": \"Quadro\",\r\n\r\n  \"role\": [\r\n    \"gestores\"\r\n  ]\r\n}\r\n<\/pre>\n<p>O campo mais importante do ponto de vista de identidade \u00e9 normalmente o <strong>sub<\/strong>, ou Subject Identifier, que \u00e9 o identificador est\u00e1vel daquele usu\u00e1rio dentro do provedor. Nome, username e at\u00e9 e-mail podem mudar. O sub n\u00e3o deveria.<\/p>\n<p><strong><\/p>\n<h2>7. Consultando as claims armazenadas pelo GeoNode<\/h2>\n<p><\/strong><\/p>\n<p>Podemos verificar exatamente o que ficou armazenado no SocialAccount. Por exemplo:<\/p>\n<pre>\r\ndocker compose exec django python manage.py shell -c \"\r\nimport json\r\n\r\nfrom allauth.socialaccount.models import SocialAccount\r\n\r\nfor account in (\r\n    SocialAccount.objects\r\n    .filter(provider='corp-idp')\r\n    .select_related('user')\r\n):\r\n    print('GeoNode:', account.user.username)\r\n    print('UID\/sub:', account.uid)\r\n\r\n    print(\r\n        json.dumps(\r\n            account.extra_data,\r\n            indent=2,\r\n            ensure_ascii=False\r\n        )\r\n    )\r\n\r\n    print()\r\n\"\r\n<\/pre>\n<p>Esse comando \u00e9 extremamente \u00fatil durante uma homologa\u00e7\u00e3o. Antes de escrever qualquer l\u00f3gica de grupos, vale confirmar: <\/p>\n<ul>\n<li>sub<\/li>\n<li>email<\/li>\n<li>preferred_username<\/li>\n<li>given_name<\/li>\n<li>family_name<\/li>\n<li>role \/ roles<\/li>\n<li>groups<\/li>\n<\/ul>\n<p>E principalmente os tipos desses atributos. Uma role pode chegar como: <strong>&#8220;role&#8221;: &#8220;gestores&#8221;<\/strong> ou <strong>&#8220;role&#8221;: [&#8220;gestores&#8221;]<\/strong><\/p>\n<p>Essas diferen\u00e7as parecem pequenas, mas fazem bastante diferen\u00e7a no c\u00f3digo.<\/p>\n<p><strong><\/p>\n<h2>8. GeoNode j\u00e1 possui mecanismo para sincronizar grupos<\/h2>\n<p><\/strong><\/p>\n<p>Aqui existe uma parte interessante do GeoNode que nem sempre \u00e9 muito conhecida. O projeto possui um mecanismo de <strong><em>Profile Extractors<\/em><\/strong>. A pr\u00f3pria classe base explica que extractors customizados podem ser registrados atrav\u00e9s de: SOCIALACCOUNT_PROFILE_EXTRACTORS<\/p>\n<p>Existe tamb\u00e9m um <strong>OpenIDExtractor<\/strong>. Entre outras coisas, ele possui:<\/p>\n<pre>\r\ndef extract_groups(self, data):\r\n    return data.get(\"groups\", \"\")\r\n\r\ndef extract_roles(self, data):\r\n    return data.get(\"roles\", \"\")\r\n<\/pre>\n<p>Perceba um detalhe. Ele procura <strong>roles <\/strong>no plural. Mas nosso Identity Provider fict\u00edcio retorna <strong>role <\/strong>no singular. \u00c9 exatamente a\u00ed que um pequeno extractor customizado pode ser \u00fatil.<\/p>\n<p><strong><\/p>\n<h2>9. Criando um extractor espec\u00edfico<\/h2>\n<p><\/strong><\/p>\n<p>Vamos criar esse extractor na pasta src\/geonode_project\/corporate_auth. <\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Arvore-de-Projeto-e-Snippets-Python.png\" alt=\"\" width=\"1774\" height=\"887\" class=\"aligncenter size-full wp-image-10618\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Arvore-de-Projeto-e-Snippets-Python.png 1774w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Arvore-de-Projeto-e-Snippets-Python-300x150.png 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Arvore-de-Projeto-e-Snippets-Python-1024x512.png 1024w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Arvore-de-Projeto-e-Snippets-Python-768x384.png 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Arvore-de-Projeto-e-Snippets-Python-1536x768.png 1536w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Arvore-de-Projeto-e-Snippets-Python-600x300.png 600w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Arvore-de-Projeto-e-Snippets-Python-945x473.png 945w\" sizes=\"auto, (max-width: 1774px) 100vw, 1774px\" \/><\/p>\n<p>Primeiro o arquivo profileextractors.py:<\/p>\n<pre>\r\nimport logging\r\n\r\nfrom geonode.people.profileextractors import (\r\n    OpenIDExtractor,\r\n)\r\n\r\n\r\nlogger = logging.getLogger(__name__)\r\n\r\n\r\nclass CorporateOpenIDExtractor(OpenIDExtractor):\r\n\r\n    ROLE_MAPPING = {\r\n        \"publico\": \"publico\",\r\n        \"analistas\": \"analistas\",\r\n        \"gestores\": \"gestores\",\r\n\r\n        # Compatibilidade com outra nomenclatura\r\n        \"role_publico\": \"publico\",\r\n        \"role_analistas\": \"analistas\",\r\n        \"role_gestores\": \"gestores\",\r\n    }\r\n\r\n    def extract_roles(self, data):\r\n\r\n        roles = data.get(\"role\", [])\r\n\r\n        if isinstance(roles, str):\r\n            roles = [roles]\r\n\r\n        mapped_roles = [\r\n            self.ROLE_MAPPING[role]\r\n            for role in roles\r\n            if role in self.ROLE_MAPPING\r\n        ]\r\n\r\n        unknown_roles = [\r\n            role\r\n            for role in roles\r\n            if role not in self.ROLE_MAPPING\r\n        ]\r\n\r\n        if unknown_roles:\r\n            logger.warning(\r\n                \"OIDC retornou roles n\u00e3o mapeadas: %s\",\r\n                unknown_roles,\r\n            )\r\n\r\n        return mapped_roles\r\n<\/pre>\n<p>Perceba que esse c\u00f3digo n\u00e3o implementa autentica\u00e7\u00e3o. Ele tamb\u00e9m n\u00e3o implementa a associa\u00e7\u00e3o ao grupo. Sua \u00fanica responsabilidade \u00e9:<\/p>\n<p>claim do IdP<br \/>\n      \u2193<br \/>\nnormaliza\u00e7\u00e3o<br \/>\n      \u2193<br \/>\nslug conhecido pelo GeoNode<\/p>\n<p><strong><\/p>\n<h2>10. Registrando o extractor<\/h2>\n<p><\/strong><\/p>\n<p>Vamos registrar o extractor no arquivo src\/geonode_project\/corporate_settings.py:<\/p>\n<pre>\r\nSOCIALACCOUNT_PROFILE_EXTRACTORS = dict(\r\n    SOCIALACCOUNT_PROFILE_EXTRACTORS\r\n)\r\n\r\nSOCIALACCOUNT_PROFILE_EXTRACTORS[\r\n    \"corp-idp\"\r\n] = (\r\n    \"geonode_project.\"\r\n    \"geonode.corporate_auth.\"\r\n    \"profileextractors.\"\r\n    \"CorporateOpenIDExtractor\"\r\n)\r\n<\/pre>\n<p>Para testar se funcionou, voc\u00ea pode usar o comando abaixo:<\/p>\n<pre>\r\ndocker compose exec django python manage.py shell -c \"\r\nfrom geonode.people.adapters import get_data_extractor\r\n\r\nextractor = get_data_extractor('corp-idp')\r\n\r\nprint(\r\n    extractor.__class__.__module__\r\n    + '.'\r\n    + extractor.__class__.__name__\r\n)\r\n\"\r\n<\/pre>\n<p>O resultado esperado \u00e9: <em>geonode_project.corporate_auth.profileextractors.CorporateOpenIDExtractor<\/em><\/p>\n<p>Por \u00faltimo, vamos validar o adapter:<\/p>\n<pre>\r\ndocker compose exec django python manage.py shell -c \"\r\nfrom django.conf import settings\r\nfrom geonode.people.adapters import get_data_extractor\r\n\r\nprint(\r\n    'SETTINGS:',\r\n    settings.SETTINGS_MODULE\r\n)\r\n\r\nprint(\r\n    'ADAPTER:',\r\n    settings.SOCIALACCOUNT_ADAPTER\r\n)\r\n\r\nprint(\r\n    'EXTRACTOR:',\r\n    get_data_extractor('corp-idp')\r\n)\r\n\"\r\n<\/pre>\n<p>O resultado esperado \u00e9:<\/p>\n<pre>\r\nSETTINGS:\r\ngeonode_project.corporate_settings\r\n\r\nADAPTER:\r\ngeonode.people.adapters.GenericOpenIDConnectAdapter\r\n\r\nEXTRACTOR:\r\n<...CorporateOpenIDExtractor object...>\r\n<\/pre>\n<p><strong><\/p>\n<h2>11. Criando os grupos no GeoNode<\/h2>\n<p><\/strong><\/p>\n<p>No GeoNode, acesse <strong>Grupos \u2192 Criar um Novo Grupo<\/strong> e crie P\u00fablico, Analistas e Gestores, garantindo que os slugs sejam respectivamente publico, analistas e gestores.<\/p>\n<p>Agora criamos os GroupProfile: P\u00fablico, Analistas e Gestores com os seguintes slugs: publico, analistas e gestores. Esse detalhe \u00e9 importante. O mecanismo de associa\u00e7\u00e3o procura o grupo pelo slug, n\u00e3o pelo t\u00edtulo visual. <\/p>\n<p>Ent\u00e3o: <strong>role = gestores <\/strong><\/p>\n<p>Precisa resular em: <strong>slug = gestores<\/strong><\/p>\n<p><strong><\/p>\n<h2>12. O que acontece no login?<\/h2>\n<p><\/strong><\/p>\n<p>O GeoNode possui uma rotina interna para sincronizar grupos provenientes da conta social. O c\u00f3digo atual busca o extractor correspondente ao provider, extrai groups ou roles, localiza o GroupProfile pelo slug e executa join(user). <\/p>\n<p>O GenericOpenIDConnectAdapter chama essa sincroniza\u00e7\u00e3o tanto na cria\u00e7\u00e3o da conta quanto em logins posteriores. GitHub O fluxo fica:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Mapeamento-de-Funcoes-de-Usuario_3.png\" alt=\"\" width=\"1672\" height=\"428\" class=\"aligncenter size-full wp-image-10602\" srcset=\"https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Mapeamento-de-Funcoes-de-Usuario_3.png 1672w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Mapeamento-de-Funcoes-de-Usuario_3-300x77.png 300w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Mapeamento-de-Funcoes-de-Usuario_3-1024x262.png 1024w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Mapeamento-de-Funcoes-de-Usuario_3-768x197.png 768w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Mapeamento-de-Funcoes-de-Usuario_3-1536x393.png 1536w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Mapeamento-de-Funcoes-de-Usuario_3-600x154.png 600w, https:\/\/www.fernandoquadro.com.br\/html\/wp-content\/uploads\/2026\/10\/Fluxo-de-Mapeamento-de-Funcoes-de-Usuario_3-945x242.png 945w\" sizes=\"auto, (max-width: 1672px) 100vw, 1672px\" \/><\/p>\n<p>\u00c9 uma solu\u00e7\u00e3o interessante porque aproveita a infraestrutura que j\u00e1 existe no GeoNode em vez de criar um segundo sistema paralelo de autoriza\u00e7\u00e3o.<\/p>\n<p><strong><\/p>\n<h2>13. Sincroniza\u00e7\u00e3o em logins seguintes<\/h2>\n<p><\/strong><\/p>\n<p>Essa parte \u00e9 especialmente importante em ambientes corporativos. Imagine:<\/p>\n<p>Na segunda-feira o usu\u00e1rio &#8220;fernando.quadro&#8221; foi colocado na role analistas. O usu\u00e1rio acessa o GeoNode e \u00e9 inserido no grupo Analistas. Posterior a essa data o administrador alterar o perfil desse usu\u00e1rio para a role gestores. <\/p>\n<p>No pr\u00f3ximo login, a associa\u00e7\u00e3o pode ser sincronizada novamente. Na vers\u00e3o atual do c\u00f3digo do GeoNode, inclusive, existem estrat\u00e9gias expl\u00edcitas para sincroniza\u00e7\u00e3o dos grupos sociais, e o adapter continua executando essa l\u00f3gica no pre_social_login. <\/p>\n<p>Isso permite tratar o provedor corporativo como fonte da verdade da associa\u00e7\u00e3o de grupos.<\/p>\n<div style=\"\n    margin: 28px 0;\n    padding: 18px 20px;\n    background: #fff8e6;\n    border-left: 5px solid #d49b20;\n    border-radius: 6px;\n    color: #2d2d2d;\n    line-height: 1.65;\n    box-shadow: 0 2px 8px rgba(0,0,0,0.06);\n\"><\/p>\n<div style=\"\n        font-size: 1.05em;\n        font-weight: 700;\n        margin-bottom: 8px;\n        color: #8a6415;\n    \"> \u26a0 Aten\u00e7\u00e3o\n    <\/div>\n<p style=\"margin: 0 0 10px 0;\">\n        Na estrat\u00e9gia nativa apresentada, durante o login social o GeoNode pode remover os<br \/>\n        <code style=\"\n            background: rgba(0,0,0,0.05);\n            padding: 2px 5px;\n            border-radius: 3px;\n        \">GroupProfile<\/code> atualmente associados ao usu\u00e1rio e recriar as associa\u00e7\u00f5es com base nos grupos ou roles recebidos do Identity Provider.\n    <\/p>\n<p style=\"margin: 0;\">\n        Portanto, esta abordagem \u00e9 apropriada quando o <strong>Identity Provider \u00e9 realmente<br \/>\n        a fonte de verdade dos grupos corporativos<\/strong>. Se a mesma conta tamb\u00e9m precisar<br \/>\n        pertencer a grupos do GeoNode administrados localmente e que n\u00e3o s\u00e3o enviados pelo IdP, a estrat\u00e9gia de sincroniza\u00e7\u00e3o dever\u00e1 ser customizada.\n    <\/p>\n<\/div>\n<p><strong><\/p>\n<h2>14. E administradores?<\/h2>\n<p><\/strong><\/p>\n<p>Eu separaria duas coisas: grupo funcional e privil\u00e9gio administrativo do GeoNode. Um grupo chamado Gestores n\u00e3o significa necessariamente que esse usu\u00e1rio tem privil\u00e9gios de Staff e\/ou Superusu\u00e1rio. <\/p>\n<p>Voc\u00ea pode criar uma role especial chamada admin, com isso \u00e9 poss\u00edvel implementar uma pequena l\u00f3gica adicional, por exemplo usando um signal do django-allauth, para controlar explicitamente:<\/p>\n<pre>\r\nuser.is_staff = True\r\nuser.is_superuser = True\r\n<\/pre>\n<p>Essa separa\u00e7\u00e3o evita transformar um grupo corporativo em sin\u00f4nimo de superusu\u00e1rio da aplica\u00e7\u00e3o.<\/p>\n<p>O tratamento de administradores \u00e9 uma pol\u00edtica adicional e n\u00e3o faz parte da configura\u00e7\u00e3o m\u00ednima necess\u00e1ria para autentica\u00e7\u00e3o OIDC e sincroniza\u00e7\u00e3o de grupos deste artigo.<\/p>\n<p><strong><\/p>\n<h2>15. Verificando usu\u00e1rios e grupos<\/h2>\n<p><\/strong><\/p>\n<p>Depois da implementa\u00e7\u00e3o acima, \u00e9 interessante realizar alguns testes de auditoria r\u00e1pida:<\/p>\n<pre>\r\ndocker compose exec django python manage.py shell -c \"\r\nfrom allauth.socialaccount.models import (\r\n    SocialAccount,\r\n)\r\n\r\nfor account in (\r\n    SocialAccount.objects\r\n    .filter(provider='corp-idp')\r\n    .select_related('user')\r\n):\r\n    user = account.user\r\n    data = account.extra_data or {}\r\n\r\n    print(\r\n        user.username,\r\n        '| role(s):', data.get('role') or data.get('roles'),\r\n        '| grupos:',\r\n        [\r\n            (g.title, g.slug)\r\n            for g in user.group_list_all()\r\n        ],\r\n        '| staff:', user.is_staff,\r\n        '| superuser:', user.is_superuser,\r\n    )\r\n\"\r\n<\/pre>\n<p>A sa\u00edda ser\u00e1 algo semelhante a:<\/p>\n<pre>\r\nfernando.quadro\r\n| role(s): ['gestores']\r\n| grupos: [('Gestores', 'gestores')]\r\n| staff: False\r\n| superuser: False\r\n<\/pre>\n<p>Isso permite verificar em uma \u00fanica consulta claim recebida, role, grupo no GeoNode e status administrativo.<\/p>\n<p><strong><\/p>\n<h2>16. Conclus\u00e3o<\/h2>\n<p><\/strong><\/p>\n<p>Integrar o GeoNode a um Identity Provider via OpenID Connect n\u00e3o precisa significar criar uma grande camada customizada de autentica\u00e7\u00e3o. Boa parte da infraestrutura j\u00e1 existe no pr\u00f3prio GeoNode e no django-allauth. O ponto mais importante \u00e9 entender o contrato entre os dois sistemas:<\/p>\n<ul>\n<li>Qual provider?<\/li>\n<li>Qual callback?<\/li>\n<li>Quais scopes?<\/li>\n<li>Quais claims?<\/li>\n<li>Qual identificador do usu\u00e1rio?<\/li>\n<li>Como chegam grupos e roles?<\/li>\n<li>Qual slug existe no GeoNode?<\/li>\n<\/ul>\n<p>A partir da\u00ed, um pequeno extractor pode ser suficiente para adaptar as claims corporativas \u00e0 estrutura de grupos utilizada pelo GeoNode.<\/p>\n<p>O resultado \u00e9 uma arquitetura em que a identidade fica centralizada no servi\u00e7o corporativo e o GeoNode continua respons\u00e1vel pelo que faz melhor: controlar o acesso aos recursos geoespaciais.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Quando uma infraestrutura geoespacial come\u00e7a a ser utilizada por diferentes equipes de uma organiza\u00e7\u00e3o, chega um momento em que manter usu\u00e1rios e senhas diretamente no GeoNode deixa de ser uma boa estrat\u00e9gia. O usu\u00e1rio j\u00e1 possui uma identidade corporativa. Ele&#8230; <a class=\"more-link\" href=\"https:\/\/www.fernandoquadro.com.br\/html\/2026\/10\/05\/geonode-openid-connect-autenticacao-corporativa-e-sincronizacao-de-grupos-na-pratica\/\">Continue lendo &rarr;<\/a><\/p>\n","protected":false},"author":275,"featured_media":10606,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24],"tags":[196],"class_list":["post-10592","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-gis","tag-geonode"],"_links":{"self":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/10592","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/users\/275"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/comments?post=10592"}],"version-history":[{"count":23,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/10592\/revisions"}],"predecessor-version":[{"id":10620,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/posts\/10592\/revisions\/10620"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/media\/10606"}],"wp:attachment":[{"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/media?parent=10592"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/categories?post=10592"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fernandoquadro.com.br\/html\/wp-json\/wp\/v2\/tags?post=10592"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}