GithubHelp home page GithubHelp logo

snok / django-auth-adfs Goto Github PK

View Code? Open in Web Editor NEW
270.0 17.0 98.0 5.28 MB

A Django authentication backend for Microsoft ADFS and AzureAD

Home Page: http://django-auth-adfs.readthedocs.io/

License: BSD 2-Clause "Simplified" License

Python 75.62% HTML 0.45% PowerShell 23.93%
adfs django jwt oauth2 authentication restframework drf azure-active-directory openid-connect

django-auth-adfs's Introduction

ADFS Authentication for Django

Documentation Status https://codecov.io/github/snok/django-auth-adfs/coverage.svg?branch=master

A Django authentication backend for Microsoft ADFS and Azure AD

Features

  • Integrates Django with Active Directory on Windows 2012 R2, 2016 or Azure AD in the cloud.
  • Provides seamless single sign on (SSO) for your Django project on intranet environments.
  • Auto creates users and adds them to Django groups based on info received from ADFS.
  • Django Rest Framework (DRF) integration: Authenticate against your API with an ADFS access token.

Installation

Python package:

pip install django-auth-adfs

In your project's settings.py add these settings.

AUTHENTICATION_BACKENDS = (
    ...
    'django_auth_adfs.backend.AdfsAuthCodeBackend',
    ...
)

INSTALLED_APPS = (
    ...
    # Needed for the ADFS redirect URI to function
    'django_auth_adfs',
    ...

# checkout the documentation for more settings
AUTH_ADFS = {
    "SERVER": "adfs.yourcompany.com",
    "CLIENT_ID": "your-configured-client-id",
    "RELYING_PARTY_ID": "your-adfs-RPT-name",
    # Make sure to read the documentation about the AUDIENCE setting
    # when you configured the identifier as a URL!
    "AUDIENCE": "microsoft:identityserver:your-RelyingPartyTrust-identifier",
    "CA_BUNDLE": "/path/to/ca-bundle.pem",
    "CLAIM_MAPPING": {"first_name": "given_name",
                      "last_name": "family_name",
                      "email": "email"},
}

# Configure django to redirect users to the right URL for login
LOGIN_URL = "django_auth_adfs:login"
LOGIN_REDIRECT_URL = "/"

########################
# OPTIONAL SETTINGS
########################

MIDDLEWARE = (
    ...
    # With this you can force a user to login without using
    # the LoginRequiredMixin on every view class
    #
    # You can specify URLs for which login is not enforced by
    # specifying them in the LOGIN_EXEMPT_URLS setting.
    'django_auth_adfs.middleware.LoginRequiredMiddleware',
)

In your project's urls.py add these paths:

urlpatterns = [
    ...
    path('oauth2/', include('django_auth_adfs.urls')),
]

This will add these paths to Django:

  • /oauth2/login where users are redirected to, to initiate the login with ADFS.
  • /oauth2/login_no_sso where users are redirected to, to initiate the login with ADFS but forcing a login screen.
  • /oauth2/callback where ADFS redirects back to after login. So make sure you set the redirect URI on ADFS to this.
  • /oauth2/logout which logs out the user from both Django and ADFS.

You can use them like this in your django templates:

<a href="{% url 'django_auth_adfs:logout' %}">Logout</a>
<a href="{% url 'django_auth_adfs:login' %}">Login</a>
<a href="{% url 'django_auth_adfs:login-no-sso' %}">Login (no SSO)</a>

Contributing

Contributions to the code are more then welcome. For more details have a look at the CONTRIBUTING.rst file.

django-auth-adfs's People

Contributors

777ge90 avatar dependabot[bot] avatar dicknetherlands avatar edevil avatar fholange avatar geomaciolek avatar jatindergit avatar jobec avatar jonasks avatar koendeleijer avatar marcosatti avatar marshad96 avatar mislavcimpersak avatar mwigh avatar nnadams avatar ollipa avatar peterfarrell avatar quozd avatar sandertuit avatar schallis avatar sebastianmanger avatar sergei-maertens avatar seykotron avatar sondrelg avatar stephane avatar tim-schilling avatar tmakruck avatar wardcornetteprivate avatar wreiner avatar xavirg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-auth-adfs's Issues

only wheel package available on pypi for 0.1.0

Could you please add .tar.gz package on pypi since latest version (0.1.0) is not available when doing pip install django-auth-adfs? It installs 0.0.5 version.

Here can be seen that only .whl package was created in latest build.

Users must be deleted and re-created if updating to 1.1.1

Set an unusable password on newly created user instead of leaving it empty. as implemented in f79c93f enforces all users to be deleted before migrating, otherwise it would crash with django.core.exceptions.ValidationError: {'password': ['This field cannot be blank.']} on login.

Should this be documented in the patch notes?

GET /oauth2/login?code=xxxx 401, login failed. Now what?

The middleware is redirecting me to ADFS. I authenticate, and get sent back to my redirect URI with a ?code=xxxxxxx appended. Then... nothing. I just get a Login Failed message. Looking at the source I suppose this is coming from the authentication flow in django-auth-adfs views.py. Because user = authenticate(authorization_code=code) returns None (verified by tweaking views.py to print user to the console), a 401 is returned with the Login Failed error message.

I guess I don't really understand what's supposed to happen at this point. There aren't any users in my auth_user table, but I was under the impression django-auth-adfs would create them on the fly.

I also seem to remember seeing claims being printed out in the server console in earlier tests... now I don't even see those coming across.

What could be the missing link?

The received resources parameter is invalid

Hi,

We are trying to configure ADFS SSO for Django using django-auth-adfs.
We believe we have configured things correctly however we get redirected back to the REDIRECT_URI with the following error

error_description u"MSIS9602: The received 'resource' parameter is invalid. The authorization server can not find a registered resource with the specified identifier."
error u'invalid_resource

Questions:
How can we identify if this is an ADFS issue or a django-auth-adfs issue?
Any guidance on how to either solve the issue or go about troubleshooting would be appreciated.

[DESKTOP_IP] = Ip of my desktop running nginx proxying to gunicorn. Note: Self Signed Certificates are being used.
[FQDN_OF_ADFS_SERVER] = FQDN of Adfs server
[RelyingPartyTrust] = Configured in ADFS and matches Django value
[ClientID] = Configured in ADFS and matches Django value

=====ADFS server configuration ==========

AllowedAuthenticationClassReferences : {}
AutoUpdateEnabled : False
DelegationAuthorizationRules :
EncryptionCertificateRevocationCheck : CheckChainExcludeRoot
PublishedThroughProxy : False
IssuanceAuthorizationRules : @RuleTemplate="AllowAllAuthzRule" => issue(Type =
"http://schemas.microsoft.com/authorization/claims/permit", Value="true");
SigningCertificateRevocationCheck : CheckChainExcludeRoot
WSFedEndpoint :
AdditionalWSFedEndpoint : {}
ClaimsProviderName : {}
IssuanceTransformRules :
ClaimsAccepted : {}
ConflictWithPublishedPolicy : False
EncryptClaims : True
Enabled : True
EncryptionCertificate :
Identifier : {https://[DESKTOP_IP]:10443/oauth2/login}
LastMonitoredTime : 12/31/1899 7:00:00 PM
LastPublishedPolicyCheckSuccessful :
LastUpdateTime : 12/31/1899 7:00:00 PM
MetadataUrl :
MonitoringEnabled : False
Name : Python
NotBeforeSkew : 0
EnableJWT : True
AlwaysRequireAuthentication : False
Notes :
OrganizationInfo :
ImpersonationAuthorizationRules :
AdditionalAuthenticationRules :
ProxyEndpointMappings : {}
ProxyTrustedEndpoints : {}
ProtocolProfile : WsFed-SAML
RequestSigningCertificate : {}
EncryptedNameIdRequired : False
SignedSamlRequestsRequired : False
SamlEndpoints : {}
SamlResponseSignature : AssertionOnly
SignatureAlgorithm : http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
TokenLifetime : 0
AllowedClientTypes : Public
IssueOAuthRefreshTokensTo : NoDevice

========== Django configuration ==============

checkout config.py for more settings

AUTH_ADFS = {
"SERVER": "[FQDN_OF_ADFS_SERVER]",
"CLIENT_ID": "[ClientID]",
"RESOURCE": "[RelyingPartyTrust]",
# Make sure to read the documentation about the AUDIENCE setting
# when you configured the identifier as a URL!
"AUDIENCE": "microsoft:identityserver:[RelyingPartyTrust]",
"ISSUER": "http://[FQDN_OF_ADFS_SERVER]/services/trust",
"CA_BUNDLE": "/code/CERT.pem",
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email"},
"REDIR_URI": "https://[DESKTOP_IP]:10443/oauth2/login",
}

============================================

SERVER:
parameter seems correct based on docs : http://django-auth-adfs.readthedocs.io/en/latest/configuration.html#server

CLIENT_ID:
http://django-auth-adfs.readthedocs.io/en/latest/configuration.html#client-id
ADFS Admin confirm value is set correctly.

RESOURCE :
http://django-auth-adfs.readthedocs.io/en/latest/configuration.html#resource
Set this to the name of the Relying Party Trust you configured in ADFS (Do not see it in ADFS config provided)
ADFS admin has confirmed it is set correctly.

AUDIENCE:
http://django-auth-adfs.readthedocs.io/en/latest/configuration.html#audience
Set this to the value of the aud claim your ADFS server sends back in the JWT token. If you leave this set to None this claim will not be verified.
Changed to None, no difference

CA_BUNDLE
http://django-auth-adfs.readthedocs.io/en/latest/configuration.html#ca-bundle
Changed to True, no difference.

CLAIM_MAPPING
http://django-auth-adfs.readthedocs.io/en/latest/configuration.html#claim-mapping
Removed, no difference.

REDIR_URI:
http://django-auth-adfs.readthedocs.io/en/latest/configuration.html#redir-uri
Seems correct based on Django ADFS plugin & ADFS redirecting back to that page and displaying errors at that URL

'TENANT_ID' is not a valid configuration directive - Question

Hi,

First of all, thank you so much for this project.

I'm trying to implement the Azure Integration using this https://github.com/jobec/django-auth-adfs/blob/master/docs/azure_ad_config_guide.rst guide.

I have all required information as per example:

AUTH_ADFS = {
    "TENANT_ID": "12345678-90ab-cdef-1234-567890abcdef",
    "CLIENT_ID": "480499d2-1f78-4a58-b7bc-03ebf8780af3",
    "RELYING_PARTY_ID": "https://examplecom.onmicrosoft.com/338d463b-b82f-490b-567890abcdef",
    "AUDIENCE": "https://examplecom.onmicrosoft.com/338d463b-b82f-490b-567890abcdef",
}

When I fire up Django, It gives me a TENANT_ID error...

django.core.exceptions.ImproperlyConfigured: 'TENANT_ID' is not a valid configuration directive

Have you experienced something like that ?

I would appreciate if you could help or at least point me to the right direction.

Thank you.

Allow to set is_staff and is_superuser fields

Right now it's not possible to properly set the is_staff and is_superuser fields on the user model of Django. Blocking access to the admin section of Django.

While there is a sort of workaround mentioned in #14 it should be handled in a better way.

SSO + change user button

The idea of disabling SSO is great and its no longer needed for us to open Firefox in incognito mode if we want to use another user. What would be the best way of integrating the SSO solution, but with the option to change the user? I'd rather not do it static and dirty as I first started doing 😄

EDIT: I could say my initial solution was to create something like this, but it don't really handle logouts:

def change_user(request):
    url = provider_config.build_authorization_endpoint(request)
    password_url = url + "&prompt=login"
    return redirect(password_url)

How can I consume new claim field in user object for example "Department"?

How can I consume new claim field in user object for example "Department"?

AUTH_ADFS = {
...
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email",
"department": "department"},
"USERNAME_CLAIM": "winaccountname",
"BOOLEAN_CLAIM_MAPPING": {"is_staff": "user_is_staff",
"is_superuser": "user_is_superuser"},
"GROUP_CLAIM": "group",
...
}

Not able to return https for redirect_uri

Not able to return https for redirect_uri:
def redirect_uri(self, request): self.load_config() return request.build_absolute_uri(reverse("django_auth_adfs:callback"))
https://github.com/jobec/django-auth-adfs/blob/master/django_auth_adfs/config.py#L259

Currently manually replacing http to https
def redirect_uri(self, request): self.load_config() return request.build_absolute_uri(reverse("django_auth_adfs:callback")).replace("http://", "https://")

Im assuming ive missing a setting in either django or django-auth-adfs... scratching my head.

If there isn't an option could a fix be to add a setting?

Capturing additional AFDS claims beyond the standard Django auth user model

If I wanted to send additional claims (e.g. employee number, department, title, device name, that sort of thing) and affiliate them with the Django auth user that your package creates and authenticates, how would you recommend that I go about doing that? I've seen several guides for extending the vanilla Django auth user model, but what would be the best way to streamline the process with django_auth_adfs?

Azure AD authentication issue with trusted locations

We managed to integrate django_auth_adfs in our system. And partly it's also working with Azure AD.
but sometimes we got the following error:
ADFS server returned an error: AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a new location, you must use multi-factor authentication to access '-*****--*-0c4812*****' .

Azure AD is configured with conditional access rules. When you are in a trusted location you don't have to enter 2FA, but if you are not on a trusted location you have to enter 2FA.
When you are on a NOT trusted location, you can login fine.
When you are on a trusted location we got the error above.

ADFS server returned an error: AADSTS50076: Due to a configuration change made by your 
administrator, or because you moved to a new location, you must use multi-factor authentication to 
access '*******-*****-****-****-0c4812*****'.

Things we tried:

  • disable trusted location. (Then you can login)
  • exclude the user from conditional access rule. (then you can login)
  • disable 2 factor authentication for the user. (then you can login)
  • enable trusted location (you can not login anymore)

We found an article from Microsoft that explains exactly what happens:

https://docs.microsoft.com/en-us/azure/active-directory/develop/conditional-access-dev-guide#scenario-app-performing-the-on-behalf-of-flow

Testing observations

Having done some testing as part of my setup and I have noticed the following:

Site return redirection:
When a user hits a view/page that requires authentication the user is sent to the ADFS login screen, the user signs in and is returned to the URL defined in the “LOGIN_REDIRECT_URL”

  • Is there any way of passing the current location over to ADFS so the user can return to the same page, I’m not sure if this is a limitation of this module or ADFS. (It’s my understanding that ADFS can do this.)

  • So for me this solution works well on sites that require a login to the whole site where the user hits the login page as the first point of call rather than a site that has mixed content which displays differently depending on your authentication state.

Logout:
I’m getting mixed results when it comes to logging out a user.

  • I assume I would need to logout of both Django and close the ADFS session, logging out of Django is not enough as I found that when I revisit the login process I get automatically authenticated (no ADFS sign in required)

  • Having a function in your code to provide this in a single template call much like the {{ ADFS_AUTH_URL }} context call would be helpful.

User “Staff” and “Superuser” Status
I didn't see an option to be able to add a user with "Staff" or "Superuser" rights

  • My thoughts were to have two groups one for Users and another for Admins, I have this working to a point, my user gets placed in to the relevant group in django based on the AD group membership however I'm unable to assign "Staff" or "Supperuser" rights to the user as its added.

Django Rest Framework integration

Hello,
In the closed issue #21 you mention that integration with DRF is on your to-do list. Any progress on this the last year?
Everything works perfect on the Django part, but we would like to protect our API with the same logic as we use for front-end.

If you have no intention to implement it in this package, but know how to make it possible, would providing some gists with example setup be possible?

Invalid ADFS token signing certificate

Hi,

Now that we have our resources correctly configured we are getting the following issue when CA_BUNDLE = False

Exception Type: ImproperlyConfigured
Exception Value: Invalid ADFS token signing certificate

Looking at the code, it seems to relate to the load_pem_x509_certificate(certificate, backend)
method in backend.py

I am using Python 2.7.13.

Working with on ADFSv2

Hi,
In the doc you mention ADFSv3 as a prerequisite. Our customer is using ADFSv2, did you try on ADFSv2?
Kind regards
Didier

Integrating this into a system

Could you perhaps provide any help with setting this up.
Previously when implementing a SAML2 setup, I would define endpoints (/ls and /acs) in the AD server to add a relying party trust. What would the equivalent be for setting this up for ADFS 3?

I have the settings file update with the library etc, and I guess the next step would be pointing it to the correct AD server and getting that AD to add the solution as a Relying Party?
I'd appreciate any help in the matter, I think I might just be missing something small here.
Environment is python 3.5, Django 1.10 and ADFS 3.0

Mapping Claims to Related Models

I'm adding django-auth-adfs to an existing project. As per the suggestion in the Django docs, I've created related models to store additional information rather than replacing the user model with AbstractUser.

I'd like to map a returned claim to one of my related objects (in this case, storing the manager of a user).

From what I can tell from #34 and the FAQ I would need to replace the user model with one inheriting from AbstractUser to be able to store this data, rather than map a claim to a related field.

Is this correct?

I've tried to use the standard related model syntax of using __ to get to related fields in my claims dictionary with no success.

Documentation Simplify Redirect path

Hi,

I just wanted to point out that in AD 4.0 you can just specify the Redirect URL http://127.0.0.1:8000 and
leave out the oauth2/callback part because, ADFS will now work with any path under that Redirect URL.

This is to simplify documentation and configuration. Give it a try and let me know!

I've tested this in AD 4.0, don't know if this applies for AD 3.0 or Azure though.

Thanks for the new release!.

Azure AD wants a new refresh token (AADSTS54005)

Authenticating against an Azure AD-Directory I'm getting this response:
django_auth_adfs ERROR ADFS server returned an error: AADSTS54005: OAuth2 Authorization code was already redeemed, please retry witha new valid code or use an existing refresh token.
I found this on Microsoft Forum.
Is this a known problem and is there a solution?

Integration with Django Rest Framework

Hi,
I am glad I found your project because I kinda struggling to set an authentication flow with one of my client's ADFS.
I developed a web app with Vue JS & Django Rest Framework.
I wondered if you would have some insights to share about making Django Rest Framework & Django Auth ADFS work together.

Thanks !

/accounts/login/?next=/my/view

Hi,

I configured my settings file and ADFS as per the guide. Now my problem is that I don't see any errors in the console or at the server in the Event Viewer. The only behavior I'm getting from visiting a @login_required view is automatic redirection to http://127.0.0.1:8000/accounts/login?next=/my/view

What pointers could you give me to see what I am missing in my configuration to allow users authenticate in my ADFS and then being redirected to the @login_required view?

Thanks

Templates not included

Despite 8baf088, it looks like the templates are still not included in the latest published PyPI package:

pip freeze | grep adfs
django-auth-adfs==1.1.2
ls $VIRTUAL_ENV/lib/python3.6/site-packages/django_auth_adfs/
backend.py  drf-urls.py  middleware.py  rest_framework.py  urls.py
config.py   __init__.py  __pycache__    signals.py         views.py

As you can see, the templates dir isn't present.

Unable to sign in with Azure AD

I get the following error once I click on login:

Sorry, but we’re having trouble with signing you in.

AADSTS50011: The reply url specified in the request does not match the reply urls configured for the application: 'xxxxxx-xxxxxx-xxxxxx-xxxxxx-xxxxxx'. <---

the application ID is the native app on Azure

My settings are


LOGIN_URL = 'django_auth_adfs:login'
LOGIN_REDIRECT_URL = '/'


MIDDLEWARE = [
    '...
'django_auth_adfs.middleware.LoginRequiredMiddleware',
]


AUTH_ADFS = {
    "LOGIN_EXEMPT_URLS":["^system/admin/",
                         "^account/login/"
                         "^oauth2/login/",
#                         "^oauth2/callback/",
                         ],
    "TENANT_ID": "xxxxx",
    "CLIENT_ID": "xx",
    "RELYING_PARTY_ID": "https://xxxx.onmicrosoft.com/xxxx-xxxx-xxx",
    "AUDIENCE": "https://xxxx.onmicrosoft.com/xxxx-xxxx-xxxx-xxxx",
    "CLAIM_MAPPING": {"first_name": "given_name",
                     "last_name": "family_name",
                     "email": "email"},
    "REDIR_URI": "https://our-website.com/oauth2/callback",
}

The query string parameters in the URL has redirect_uri as: http://our-website.com/oauth2/callback

In Azure the reply url is set to https://our-website.com/oauth2/callback

Is there anything that I'm missing? Should I specify this reply url somewhere in my settings?
Django 1.11 and django-auth-adfs 1.0.0

Please let me know if you need any more info from my side

'login failed'

Hey there! Not sure if this is a bug or if this is user error (probably the latter), but I'm trying to integrate this application with Davidson College's ADFS.

I've configured relying party trust and added AUTH_ADFS to settings.py, but upon calling /oauth2/login, we get a simple "login failed." With verbose debugging on, we receive the error django_auth_adfs.backend django_auth_adfs was called but no authorization code was received

My assumption is that this is the link that should redirect us to our ADFS login page. Is this assumption incorrect?

Config settings below.

AUTH_ADFS = {

    "SERVER": "sso.example.com",
    "CLIENT_ID": "django.example.com",
    "RESOURCE": "django.example.com",
    # Make sure to read the documentation about the AUDIENCE setting
    # when you configured the identifier as a URL!
    "AUDIENCE": "microsoft:identityserver:django.example.com",
    "ISSUER": "https://sso.example.com/adfs/services/trust",
    "CA_BUNDLE": False,
    "CLAIM_MAPPING": { "emailaddress": "emailaddress",
                      "givenname": "givenname",
                      "surname": "surname",
                      "Group": "Group",
                      "windowsaccountname": "windowsaccountname",
                      "groupsid": "groupsid",
                      "title": "title",
                      "department": "department" },
    "USERNAME_CLAIM": "winaccountname",
    "GROUP_CLAIM": "group",
    "REDIR_URI": "http://django.example.com/oauth2/login",

}

Thank you for your help and patience with my ignorance!

ConnectionError preventing login

Good morning,

Did what I could to verify that this wasn't something easily fixable, but one of my two sites using auth-adfs is having issues this morning:

HTTPSConnectionPool(host='login.microsoftonline.com', port=443): Max retries exceeded with url: /<tenant-id>/.well-known/openid-configuration (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x042E97D0>: Failed to establish a new connection: [Errno 11002] getaddrinfo failed',))

I haven't changed anything, and the other site using an identical setup isn't currently facing any issues. Did I misconfigure something or am I missing something obvious?

Let me know if you'd like me to post any more of the traceback, I'll update this with anything else I find relevant.

Thank you!

AdfsBackend.authenticate() not called...

I have a problem with django-auth-adfs: Login always failed.

I can see that django_auth_adfs.backend.AdfsBackend.authenticate() will be never called.

The signature of this method is authenticate(self, authorization_code=None) and django.contrib.auth.authenticate() will skip the call because of:

try:
    inspect.getcallargs(backend.authenticate, **credentials)
except TypeError:
    # This backend doesn't accept these credentials as arguments. Try the next one.
    continue

TypeError will be raised because **credentials dict has username and password arguments...

So i think there is a missing part somewhere ?!?

Any hints?

Does this package work with Django Rest Framework / Angular

I noticed that you mentioned it was on your plan to make this work with DRF. I didn't realize that was the case and tried to get it working but have only gotten to a "DEBUG:django_auth_adfs.backend:django_auth_adfs was called but no authorization code was received
"GET /oauth2/login HTTP/1.1" 401 12
WARNING:django.server:"GET /oauth2/login HTTP/1.1" 401 12"

Just wondering if this should work for DRF or if not do you know of any other options for DRF and ADFS integration

ProgrammingError at /oauth2/callback: Table does not exist

With django_auth_adfs enabled trying to manipulate the auth_group table in any way will error, saying it it unable to find the table. The table however exists, and doing the same manipulations with the module disabled (adding users and groups) will work fine.

website.com and db are placeholders.

Environment:
Request Method: GET
Request URL: http://website.com:8090/oauth2/callback?code=iGSphxaiNECS46oNq94OUA.Dz4A-ntm1ggIAOhiFot6zkGSYv8.f_IUukDnfSynaji5Br3YLHcx9Eq_7Wa6DW3ujG56R1qKgpNrwXuCMhRMzz4B9B5rxrvj1krZeyleaO7wlBVtTEixtaMTo3KhseX-myd3iITe_AWDSvllckGD_88dXj8r_QXlylkdeRi0CLMptwOomkwO6oqm5fOnVdyLKMby3G3rUnwlzysmcI7Se_zdUnTBcVQc5ecDzNmtLuN8zfzMKFzUsxpZ5la4nDav0PhfAXFXqhsCfiwWqphZKGamxdD4SOlgOjwYnTXp-J7Cyf588oemdy95u1NC528GykBpdEO99yvxGAT81Kg5O1sZ8G5AngOfg59Q6JdRCmBDvV3wMQ&state=Lw==

Django Version: 2.0.4
Python Version: 3.6.3
Installed Applications:
['django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'console',
 'django_celery_results',
 'corsheaders',
 'django_filters',
 'django_extensions',
 'widget_tweaks',
 'django_tables2',
 'haystack',
 'rangefilter',
 'storages',
 'django_auth_adfs']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'corsheaders.middleware.CorsMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django_auth_adfs.middleware.LoginRequiredMiddleware']

Traceback:

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/backends/utils.py" in _execute
  85.                 return self.cursor.execute(sql, params)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/backends/mysql/base.py" in execute
  71.             return self.cursor.execute(query, args)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/cursors.py" in execute
  250.             self.errorhandler(self, exc, value)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/connections.py" in defaulterrorhandler
  50.         raise errorvalue

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/cursors.py" in execute
  247.             res = self._query(query)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/cursors.py" in _query
  412.         rowcount = self._do_query(q)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/cursors.py" in _do_query
  375.         db.query(q)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/connections.py" in query
  276.             _mysql.connection.query(self, query)

The above exception ((1146, "Table 'db.auth_group' doesn't exist")) was the direct cause of the following exception:

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
  35.             response = get_response(request)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  128.                 response = self.process_exception_by_middleware(e, request)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  126.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/views/generic/base.py" in view
  69.             return self.dispatch(request, *args, **kwargs)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/views/generic/base.py" in dispatch
  89.         return handler(request, *args, **kwargs)

File "/home/watson/.local/lib/python3.6/site-packages/django_auth_adfs/views.py" in get
  34.         user = authenticate(request, authorization_code=code)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/contrib/auth/__init__.py" in authenticate
  70.             user = _authenticate_with_backend(backend, backend_path, request, credentials)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/contrib/auth/__init__.py" in _authenticate_with_backend
  116.     return backend.authenticate(*args, **credentials)

File "/home/watson/.local/lib/python3.6/site-packages/django_auth_adfs/backend.py" in authenticate
  266.         user = self.process_access_token(access_token, adfs_response)

File "/home/watson/.local/lib/python3.6/site-packages/django_auth_adfs/backend.py" in process_access_token
  96.         self.update_user_groups(user, claims)

File "/home/watson/.local/lib/python3.6/site-packages/django_auth_adfs/backend.py" in update_user_groups
  170.             django_groups = [group.name for group in user.groups.all()]

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/models/query.py" in __iter__
  272.         self._fetch_all()

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/models/query.py" in _fetch_all
  1179.             self._result_cache = list(self._iterable_class(self))

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/models/query.py" in __iter__
  53.         results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/models/sql/compiler.py" in execute_sql
  1067.             cursor.execute(sql, params)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/backends/utils.py" in execute
  100.             return super().execute(sql, params)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/backends/utils.py" in execute
  68.         return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/backends/utils.py" in _execute_with_wrappers
  77.         return executor(sql, params, many, context)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/backends/utils.py" in _execute
  85.                 return self.cursor.execute(sql, params)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/utils.py" in __exit__
  89.                 raise dj_exc_value.with_traceback(traceback) from exc_value

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/backends/utils.py" in _execute
  85.                 return self.cursor.execute(sql, params)

File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/django/db/backends/mysql/base.py" in execute
  71.             return self.cursor.execute(query, args)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/cursors.py" in execute
  250.             self.errorhandler(self, exc, value)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/connections.py" in defaulterrorhandler
  50.         raise errorvalue

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/cursors.py" in execute
  247.             res = self._query(query)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/cursors.py" in _query
  412.         rowcount = self._do_query(q)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/cursors.py" in _do_query
  375.         db.query(q)

File "/home/watson/.local/lib/python3.6/site-packages/MySQLdb/connections.py" in query
  276.             _mysql.connection.query(self, query)

Exception Type: ProgrammingError at /oauth2/callback
Exception Value: (1146, "Table 'db.auth_group' doesn't exist")

calling AdfsBackend.authenticate()

I am done with minimal configurations as exactly suggested by docs (http://django-auth-adfs.readthedocs.io/en/latest/configuration.html --> settings section).
The middleware is redirecting me to ADFS. I authenticate, and get sent back to my REDIR_URL with a #code=xxxxxxx appended.

Currently I have managed to capture this authentication code via AJAX call.

Once we receive a authorization code, there should be token validation end point on the client side (I assume that is resolved by CLIENT_ID in settings.py)
How should the subsequent flow be? I think that link is missing somewhere.

Login failed always returned

I've tried configuring this and all i get is Login failed after I've submitted my credentials to ADFS. I get t a long code response in the browser but nothing else.

Duplicate Key Integrity Error - Second login and beyond

Working on a Django project (specifically with the Saleor framework) and looking to authenticate against our ADFS 2016 servers. I can get the authentication to work, but only when the user logs in for the first time. After they log in for the first time, proceeds to log out, and tries to log in again...a "duplicate key value violates unique constraint "userprofile_user_email_key" IntegrityError is thrown. The details says the email address already exists.

Request Method: | GET
-- | --
http://localhost:8000/en/oauth2/callback?code=yRuy1iPDyUu3PlGaHqcHpw.l0dBl0hc1ghgAPxu-bZKE6AXCIY.gf96MEQm8zNy8IJQGotqh1JiBshH5n7WOC8ivsqPiPafJvakuapOmRtyj1_V92nUFjN6xgXgOkcI-TSwHQu6wfrIva5nMTf6dvMU9qFWji3Gy1jdn8zVhgh4x2kTyjialSKPsWEXhdrkESm9VaHPVl17j5Y89MlTqJpF2H6FJkKx0w1yACjsbf9Ua8vI3haVHd45UGqy8ElOSmPSYhXmPzLgyStBXQqKGXU_B7FJ8WLRSIqA2DDPi07BLPtACyeCPyXboaGl-ZoUcZtyv3T4af2fkwzzHy3OB387BbVIJFLahrFgDGAPdV5bdevfe_YMVXSzHpx34xEDaD4OH9UIBQ&state=Lw==&client-request-id=785f2896-595c-40f2-e731-0780010000fc
2.1.3
IntegrityError
duplicate key value violates unique constraint "userprofile_user_email_key" DETAIL:  Key (email)=([email protected]) already exists.
/usr/local/lib/python3.6/site-packages/django/db/backends/utils.py in _execute, line 85
/usr/local/bin/python
3.6.7
['/app',  '/usr/local/lib/python36.zip',  '/usr/local/lib/python3.6',  '/usr/local/lib/python3.6/lib-dynload',  '/usr/local/lib/python3.6/site-packages',  '/app']
Fri, 7 Dec 2018 07:33:36 -0600

Its my understanding (please correct me if im wrong), when a user a logs in via ADFS, if the user doesn't exist...the user will be created in the Django Postgres database. If the user does exist, a "mapping" will happen and associate the ADFS user to the existing user in the Django Postgres database?

Our AUTH_ADFS config is as follows (had to scrub our information):

AUTH_ADFS = {
    "SERVER": "scrubbed URL",
    "CLIENT_ID": "scrubbed client_ID",
    "RELYING_PARTY_ID": "scrubbed relying party ID",
    "AUDIENCE": "microsoft:identityserver:scrubbed",
    "CA_BUNDLE": "/app/ca-bundle.pem",
    "CLAIM_MAPPING": {"email":"email"},
    "GROUP_TO_FLAG_MAPPING": {"is_superuser": "Admins"},
    "CONFIG_RELOAD_INTERVAL": 0.1
}

Any ideas?

Getting this module to work

Getting different issues as I try to find my way.

I have followed the instructions best I can.

Environment:
I'm running a lab environment with a Microsoft AD FS server and Domain Controller.
Ubuntu server running my django app.

My AUTH_ADFS settings are as follows.
AUTH_ADFS = {
"SERVER": "lab.local",
"CLIENT_ID": "LogSearch",
"RESOURCE": "logsearch",
#"AUDIENCE": "microsoft:identityserver:https://lab.local/adfs/oauth2/token",
#"SIGNING_CERT": "/webapps/logsearch/adfs.pem",
"ISSUER": "https://lab.local/adfs/oauth2/token",
"CA_BUNDLE": "/webapps/logsearch/adfs.pem",
#"CA_BUNDLE": "False",
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email"},
"REDIR_URI": "https://lab.local/adfs/ls",
}

I have been flipping the settings around a lot. When I do I get different errors. With the current setup:

TypeError at /oauth2/login
'instancemethod' object has no attribute 'getitem'

Request Method: GET
Request URL: http://logsearch.lab.local/oauth2/login

Django Version:
1.10.6

Exception Value:
'instancemethod' object has no attribute 'getitem'

Brg,
Patrik

How groups are consumed in Django from the claim?

Having this settings file:

AUTH_ADFS = {
....
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email"},
"USERNAME_CLAIM": "winaccountname",
"BOOLEAN_CLAIM_MAPPING": {"is_staff": "user_is_staff",
"is_superuser": "user_is_superuser"},
"GROUP_CLAIM": "group",
....
}

How the GROUP_CLAIM is consumed in Django side?

MSIS9321: Received invalid OAuth request.

I am using a Windows Server 2016 virtual machine to test the integration and I have successfully tested that the ADFS works via https://[MY-ADFDS]/adfs/ls/IdpInitiatedSignon.aspx signon test page. This is a portion that pertains to Django-ADFS in my settings.py file:

AUTH_ADFS = {
"SERVER": "[MY-ADFS]",
"CLIENT_ID": "django_website.adfs.client_id",
"RESOURCE": "django_website.adfs.identifier",
"AUDIENCE": "microsoft:identityserver:django_website.adfs.identifier",
"ISSUER": "http://[MY-ADFS]/adfs/services/trust",
"CA_BUNDLE": False,
"CLAIM_MAPPING": {"first_name": "given_name",
"last_name": "family_name",
"email": "email"},
"USERNAME_CLAIM": "winaccountname",
"GROUP_CLAIM": "group",
"REDIR_URI": "http://[MY-DJANGO-APP]/oauth2/login",
}

But still I am getting the following error:

Description:
Encountered error during federation passive request.

Additional Data

Protocol Name:
OAuthAuthorizationProtocol

Relying Party:
django_website.adfs.identifier

Exception details:
Microsoft.IdentityServer.Web.Protocols.OAuth.Exceptions.OAuthUnauthorizedClientException: MSIS9321: Received invalid OAuth request. The client 'django_website.adfs.client_id' is forbidden to access the resource 'django_website.adfs.identifier'.
at Microsoft.IdentityServer.Web.Protocols.OAuth.OAuthProtocolContext.ValidateScopes(String scopeParameter, String clientId, String relyingPartyId)
at Microsoft.IdentityServer.Web.Protocols.OAuth.OAuthAuthorization.OAuthAuthorizationRequestContext.ValidateCore()
at Microsoft.IdentityServer.Web.Protocols.ProtocolContext.Validate()
at Microsoft.IdentityServer.Web.Protocols.OAuth.OAuthAuthorization.OAuthAuthorizationProtocolHandler.GetRequiredPipelineBehaviors(ProtocolContext pContext)
at Microsoft.IdentityServer.Web.PassiveProtocolListener.EvaluateHomeRealm(PassiveProtocolHandler protocolHandler, ProtocolContext protocolContext)
at Microsoft.IdentityServer.Web.PassiveProtocolListener.OnGetContext(WrappedHttpListenerContext context)

It makes no sense to my since, I ensured that the Relying Party Trust has the Permit everyone attribute.

Do you have any suggestions?

401 Login Failed (Django) vs. An Access Token was successfully issued to client (ADFS)

Hi,

I don't know what I did to mess things up but right now in my development environment I am getting code 401 Login Failed. When I debug on the event viewer on ADFS it says that all good "An Access Token was successfully issued to client: blablabla"

I debug the package:

class OAuth2View(View):
def get(self, request):
"""
Handles the redirect from ADFS to our site.
We try to process the passed authorization code and login the user.

    Args:
        request (django.http.request.HttpRequest): A Django Request object
    """
    code = request.GET.get("code", None)

    print("Code: {}".format(code))

    user = authenticate(authorization_code=code)

    print("user{}".format(user))
    if user is not None:
        if user.is_active:
            login(request, user)
            # Redirect to the "after login" page.
            # Because we got redirected from ADFS, we can't know where the
            # user came from.
            if settings.LOGIN_REDIRECT_URL:
                return redirect(settings.LOGIN_REDIRECT_URL)
            else:
                return redirect(django_settings.LOGIN_REDIRECT_URL)
        else:
            # Return a 'disabled account' error message
            return HttpResponse("Account disabled", status=403)
    else:
        # Return an 'invalid login' error message
        return HttpResponse("Login failed", status=401)

And I placed 2 print commands in the very begging of each variable assignation. It turns out that the code is getting populated by the ADFS response on the query string but the user=authenticate command its returning None.

What could possibly be causing this behavior?

My settings:

AUTH_ADFS = {
"SERVER": '[SERVER]',
"CLIENT_ID": 'f9a487be-ae3a-47f5-b91f-2cad4e2f3f5a',
"RESOURCE": 'http://127.0.0.1:8000',
"AUDIENCE": 'http://127.0.0.1:8000',
"ISSUER": 'http://[SERVER]/adfs/services/trust',
"CA_BUNDLE": False,
"CLAIM_MAPPING": {
"email": "email",
"first_name": "given_name",
"last_name": "family_name",
},
"USERNAME_CLAIM": "winaccountname",
"GROUP_CLAIM": "group",
"BOOLEAN_CLAIM_MAPPING": {
"is_staff": "user_is_staff",
"is_superuser": "user_is_superuser"
},
"REDIR_URI": 'http://127.0.0.1:8000/oauth2/login',
}

Show error reported page when ADFS redirects back with error

The user should be shown a page when the ADFS redirects back with an error instead of a code. The current behavior is that django-auth-adfs throws an exception and the user receives a 500 Server Error page.

The situation I received this error in was:

  • A valid user in ADFS tries to login, and is redirected to the ADFS
  • ADFS authenticates the user, but the user isn't authorized to use the Relying Party
  • ADFS redirects the user to the REDIR_URI without a code, but with error and error_description URL parameters
  • Django-auth-adfs throws an exception as it can't find the "code" URL parameter

URL the ADFS redirected to: https://<hostname>/oauth2/login?error=access_denied&error_description=MSIS9619%3a+The+received+request+is+not+authorized+to+access+the+requested+resource.

Exception and traceback:

Traceback:

File "/<path>/env/lib/python3.5/site-packages/django/utils/datastructures.py" in __getitem__
  83.             list_ = super(MultiValueDict, self).__getitem__(key)


      During handling of the above exception ('code'), another exception occurred:



File "/<path>/env/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
  41.             response = get_response(request)

File "/<path>/env/lib/python3.5/site-packages/django/core/handlers/base.py" in _legacy_get_response
  249.             response = self._get_response(request)

File "/<path>/env/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "/<path>/env/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/<path>/env/lib/python3.5/site-packages/django/views/generic/base.py" in view
  68.             return self.dispatch(request, *args, **kwargs)

File "/<path>/env/lib/python3.5/site-packages/django/views/generic/base.py" in dispatch
  88.         return handler(request, *args, **kwargs)

File "/<path>/env/lib/python3.5/site-packages/django_auth_adfs/views.py" in get
  19.         code = request.GET["code"]

File "/<path>/env/lib/python3.5/site-packages/django/utils/datastructures.py" in __getitem__
  85.             raise MultiValueDictKeyError(repr(key))

Exception Type: MultiValueDictKeyError at /oauth2/login
Exception Value: "'code'"

Version from pip freeze: django-auth-adfs==0.1.2

Steps to set up authorization in ADSF: https://social.technet.microsoft.com/Forums/en-US/87bb6180-e7e1-4c64-9fbc-4596dfae42bb/adfs-authentication-based-on-group-membership?forum=ADFS

unsupported or invalid wheel

pip install django-auth-adfs==0.1.2 fails with django-auth-adfs is in an unsupported or invalid wheel when trying to install on Python 3.6. Installation works fine on Python 2.7.

Active Directory Functional Level

I currently have django-auth-adfs working using Active Directory Functional Level Windows Server 2008R2. My organization is planning to upgrade to the Windows Server 2016 functional level.

Is django-auth-adfs certified to run on the 2012R2 Domain Functional Level?
Are you aware of any known bugs or issues running with 2012R2 Domain Functional Level?

Is django-auth-adfs certified to run on 2016 Domain Functional Level?
Are you aware of any known bugs or issues running with 2016 Domain Functional Level?

Thanks for your help.

500 Response: authenticate() takes exactly 0 arguments (2 given)

I'm trying to plug this module into askbot and I continue getting an error on authenticate() takes exactly 0 arguments (2 given) which does not make much sense to me since the method clearly is asking for two args. Any help is much appreciated since I'm at my limits here.

Settings

# checkout the documentation for more settings
AUTH_ADFS = {
    'TENANT_ID': 'TENANT_ID',
    'CLIENT_ID': 'AZURE_CLIENT_ID',
    'CLIENT_SECRET': 'AZURE_CLIENT_SECRET',
    'RELYING_PARTY_ID': 'AZURE_CLIENT_ID',
    'AUDIENCE': 'microsoft:identityserver:unique-name',
    'CLAIM_MAPPING': {'first_name': 'given_name',
                      'last_name': 'family_name',
                      'email': 'email'},
    'REDIR_URI': 'https://CSRF_DOMAIN/oauth2/login',
}

Python 2.7, Django 1.8 failure:


Request Method: | GET
-- | --
https://url.com/oauth2/callback?code=lotsofalphnumericcharaterslikeakeyhere&state=Lw%3d%3d&session_state=123c85a7-1234-1234-21c6-12a9a96e123a
1.8.19
TypeError
authenticate() takes exactly 0 arguments (2 given)
/usr/local/lib/python2.7/dist-packages/django_auth_adfs/views.py in get, line 34
/usr/local/bin/uwsgi
2.7.12
['.',  '',  '/usr/local/lib/python2.7/dist-packages/pip-19.0.3-py2.7.egg',  '/usr/lib/python2.7',  '/usr/lib/python2.7/plat-x86_64-linux-gnu',  '/usr/lib/python2.7/lib-tk',  '/usr/lib/python2.7/lib-old',  '/usr/lib/python2.7/lib-dynload',  '/usr/local/lib/python2.7/dist-packages',  '/usr/lib/python2.7/dist-packages',  '.',  '/usr/local/lib/python2.7/dist-packages/askbot/deps']
Thu, 21 Feb 2019 03:55:07 +0000


Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.