Giter Club home page Giter Club logo

omniauth-keycloak's People

Contributors

alexpetrov avatar cambero avatar ccrockett avatar dependabot[bot] avatar frenesim avatar hobbypunk90 avatar kazhuu avatar masonhensley avatar mcelaney avatar offner avatar riz-v 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

Watchers

 avatar

omniauth-keycloak's Issues

Adding parameters and http headers to omniauth authorize path helper

I use the path helper [:scope]_[:provider]_omniauth_authorize_path to redirect to my keylcoak (OAuth2/OpenIDConnect) auth service.

To set the UI language on the auth login page, one can either set an Accept-Language http header or add a query parameter to the endpoint's url.

I wonder, if there is any way to pass query parameters and/or headers to the authorization request via the url helper. I found out, that one can do something like user_keycloakopenid_omniauth_authorize_path(ui_locale: 'en'), but this does add the parameter to the redirect_uri, not to the top level request url.

Dependency update - json-jwt

Hi! Do you guys plan to update the dependencies so OpenSSL 3 can be used? From what I could make out, only json-jwt would need to be updated.

Cheers

When in test mode, setup phase tries to fetch Keycloak configuration

I think the during the test mode setup phase should skip fetching the Keycloak configuration, so when testing you don't need to rely upon having a running Keycloak instance somewhere. This now makes it quite hard to test this omniauth strategy because of this requirement.

I haven't looked at other omniauth strategies what they do but according to omniauth testing wiki page. Omniauth is just supposed to redirect the user to the callback url with the mocked result. Now because of this external config fetch request, testing the callback behavior is harder than it needs to be I think.

I'm on the right track with this? I'd happy to make a PR about this.

EDIT:
I actually managed to test the login with the browser by mocking the keycloak configuration and certs api locally with WebMock. Either way I think code should not make external requests in test mode.

No route matches [POST] "/auth/keycloak_openid"

Hi,
first of all thanks for this gem.

I just create a new rails 6.1 project and first thing to me is to get auth working.. so there is nothing installed except this gem plus its dependencies..

i have configured this initializer:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :keycloak_openid, 'xxxCLIENTxxx', 'xxxKEYxxx',
           client_options: {site: 'https://auth.xxxDOMAINxxx.com', realm: 'xxxREALMxxx'}
end

this route:

post '/auth/:provider/callback', to: 'sessions#create'

and just for testing a quick and dirty

<%= button_to 'Sign In', '/auth/keycloak_openid' %>

to get the ball rolling.. i click on it and get this No route matches [POST] "/auth/keycloak_openid"error.

however, Omniauth is present and its config suggests the keycloak_openid provider is too:

image

i also gave auth/keycloak a shot (yeah, proly made no sense, but just in case)... - same thing

idk what i did wrong here, maybe its a bug (with rails 6.1)..(?)

Handle exception occuring in set_up phase to prevent crash

Hi,

When an error occurs in setup_phase (Keycloak is down for exemple), the server is crashing because of the exception at thise line:(Faraday::ConnectionFailed)

response = Faraday.get config_url

This function set_up phase is called from omniauth gem, not from the server directly

How can we catch it from the server to prevent the crash and to handle the error , please ?

Thanks for any suggesion.

Dynamically load Client and Realm

Hi,

This gem has been extremely helpful, but I'm missing a major piece to complete my implementation.

My use-case is a multi tenant approach, where the Realm and Client are expected to change.

I'm using this gem alongside of Devise and this line of devise.rb is loaded loaded only once when the application starts
config.omniauth :keycloak_openid, "Example-Client-Name", "example-secret-if-configured", client_options: { site: "https://example.keycloak-url.com", realm: "example-realm" }, :strategy_class => OmniAuth::Strategies::KeycloakOpenId

I'm forced to have a single client in this case.

Is there a possibility where I can dynamically configure the Client and Realm per request?

Any pointers on how to go about this would be really helpful.

Thanks

Issue passing params to callback URL

I'm using omniauth-keycloak with devise in a rails + react project and I have keycloak omniauth hooked up successfully for regular login/signup. I'm trying to add it to the devise invitation flow and am running into an issue passing the invitation_token so it is accessible in the omniauth callback endpoint.

I added the param into authorize_options and passed it as a hidden input in the form as per #24 but this doesn't seem to do anything to the callback request. I'm not sure if I'm misunderstanding the feature or if there is something wrong with the config.

I also tried passing the param directly in the form action e.g. action={'/auth/keycloak?invitation_token=${token}'} but this causes an Incorrect redirect_uri error in the callback phase, presumably due to the invitation_token in the query params. The keycloak integration redirect uri is set to http://localhost:3000/*

Relevant code:

# config/initializers/devise.rb
config.omniauth :keycloak_openid,
                  ENV["KEYCLOAK_CLIENT"],
                  ENV["KEYCLOAK_SECRET"],
                  client_options: {
                    site: ENV["KEYCLOAK_AUTH_URL"],
                    realm: "standard",
                  },
                  authorize_options: [:invitation_token],
                  name: :keycloak,
                  strategy_class: OmniAuth::Strategies::KeycloakOpenId
// form.tsx
<form action={`/api/auth/keycloak`} method="post">
    <Input hidden={true} name="invitation_token" value={invitationToken} />
    <input
        type="hidden"
        name="authenticity_token"
        value={document.querySelector("[name=csrf-token]").content}
    />
    <Button type="submit">Login</Button>
</form>

Invalid JSON Format

Hello all, I'm getting this invalid JSON format error. Since my app's ruby version is 2.2.2 I'm using gem 'omniauth-keycloak', '~> 1.1' version. After logging via keycloak I'm able to get the authorization_code using that ID token and the refresh token are also fetched. When I debugged this further, this error is happening at this point.

    header, claims, signature = input.split('.', JWS::NUM_OF_SEGMENTS).collect do |segment|
      			Base64.urlsafe_decode64 segment.to_s
    end
    **This is the setup that I used for my app.** 
    
    gem 'omniauth', '~> 1.9.0'
	gem 'omniauth-oauth2', '~> 1.6.0'
	gem 'omniauth-keycloak', '~> 1.1'
	gem 'json-jwt', '~> 1.9.4'
    		
    **Backtrace** 
    (keycloakopenid) Callback phase initiated.
	"/usr/local/rvm/gems/ruby-2.2.2/gems/json-jwt-1.9.4/lib/json/jose.rb:66:in `rescue in decode'", 
	"/usr/local/rvm/gems/ruby-2.2.2/gems/json-jwt-1.9.4/lib/json/jose.rb:60:in `decode'", 
	"/usr/local/rvm/gems/ruby-2.2.2/gems/omniauth-keycloak-1.1.0/lib/omniauth/strategies/keycloak-openid.rb:70:in `raw_info'", 

Are there any reason to specify one certificate key to decode JWT?

Hi, thanks for the amazing omniauth keycloak integration you've put on this gem!

When I'd to test some example apps with a demo keycloak instance, my keycloak instance return multiple certificate keys and I've trouble on the following code:

if (certs.status == 200)
json = MultiJson.load(certs.body)
@cert = json["keys"][0]
log :debug, "Successfully got certificate. Certificate length: #{@cert.length}"
else

Are there any reason to specify it?

Expose id_token inside extra hash of auth hash

In keycloak 17.0.0 and afterwards they have removed the support for redirect_uri on logout request. They suggest the usage of
post_logout_redirect_uri and id_token_hint. To use the RP initiated logout we therefore require the id_token . Unfortunately after digging around quite a bit, I found out that id_token is not exposed in the auth hash in omniauth-keycloak. the access_token.token is exposed , but the access_token['id_token'] is not exposed. By passing scope: 'openid' this id_token is available inside access_token['id_token']. Without this id_token it is not possible to use the RP initiated logout feature of keycloak. Do you think it is possible to expose this id_token inside the extra hash?
Or maybe there is already a hidden configuration that I am missing?

Issue with keycoak unsorted key values

Hi,
I am facing an issue with omniauth_keycloak.
Below i have sample test url ->

https://test_url.com/users/auth/keycloakopenid/callback?state=c19b6e7dff2fa008b87d0a726daa3a&session_state=da4ba031-dd24-4374-bb2b-6fda3e9045a8&code=ec954250-f1c25-ed86c846b2a6.da4ba031-dd24-4374-bb2b-6fda3e9041cfe-c933-4e16-896a-f0c04758815a

After adding my keycloak sso details I am getting below error logs

{"@timestamp": "2023-07-03T08:08:03.377+0000","@Version":"1","log_type":"LOG","log_level":"INFO","level_value":20000,"logger_name":"org.slf4j.impl.StaticLoggerBinder","service_name":"cdt-cdt","artifact_id":"NOT_SET","trace_token":"undefined","thread_name":"scala-execution-context-global-26","message": "Reconfiguring Qlogger"}

We got a solution for this issue. But we didn't understand where we need to do the changes.
the solution is we need to sort the "use" values in the keys list.
If use value as 'enc' and it is coming first into the keys list then we are getting the issue. or else it is working fine.

Could please suggest where we need to sort the below keys like rails(devise.rb file)application level or in gem (https://github.com/ccrockett/omniauth-keycloak/blob/master/lib/omniauth/strategies/keycloak-openid.rb) this page?

{
"keys": [{
"kid": "uaC38HXBBhkgaEjKavC9t0SQTTM",
"kty": "RSA",
"alg": "RS256",
"use": "enc",
"n": "xn1E1P-ceqpSLEyY7PdInW6peF5cC3DCYkch5u8ckUEJ-IwCZtzIypVhagNuRYxN0XSoYi6SL0oL83su6o_lHjKDkC5y5w",
"e": "AQAB",
"x5c": ["MIICnttG+ApMjcgq2vuQT+w=="],
"x5t": "SgLLN3V0WIISSF6I",
"x5t#S256": "OX6FsNccV2uaiKTm4b9lQju6EelZY"
}, {
"kid": "6-p2qQ_LRKc5PtYvQrysDhrUzIc",
"kty": "RSA",
"alg": "RSA-OAEP",
"use": "sig",
"n": "YN-3eOq3w6uVHWt0UkKAdIjMvkjUjjMj_DhQOqJepgWwsVThJ8IJw",
"e": "AQAB",
"x5c": ["MIICnTCCWZFxJrzndobBsTlmspxpRZotxR4LZJr6B6srzxjpuQw7jmGAl8/VQzEreDAnxCWafydCCv04lQ=="],
"x5t": "BVUvObKSA6v4TMtoMI",
"x5t#S256": "4wby9rmzuPmxIONg7TGCU"
}]
}

Scope setting gets overwritten causing ID token not to be returned from Keycloak

I noticed the version 1.5.1 of this gem added this #24 change. This change added request_phase method here. However what this is doing for me is that it's overwriting scope that I have set when configuring the provider like this:

    provider(:keycloak_openid,
             "client_id",
             "client_secret",
             name: "keycloak",
             scope: "openid", # <-- this line here!!!
             client_options: {
                 # Base url as empty, defaults to "/auth/..." base url.
                 base_url: "", site: "keycloak_url_here", realm: "test_realm"
             })

What is basically happening is the request_phase method is overwriting my scope with nil instead of "openid". This in turn causes Keycloak not to return ID token for me anymore.

I could provide a fix for this but not sure if I'm setting the scope correctly. Or could this be fixed in the gem to conditionally set value from request.params if it's nil? Maybe something along these lines:

def request_phase
  options.authorize_options.each do |key|
    options[key] = request.params[key.to_s] if options[key].nil?
  end
  super
end

Is the "/auth/" part mandatory in Keycloak's configuration URL?

To be integrated at corporate level, my RoR 5.2.4 application needs a new authentication provider: Keycloak.
Here, the Keycloak server URL to retrieve configuration information is: https://identity-a.bit.admin.ch/realms/bfs-sis-a

Which should correspond to the following configuration in config/inititalizers/devise.rb:

  config.omniauth :keycloak_openid,
    "BFS.SIS",
    client_options: { site: "https://identity-a.bit.admin.ch", realm: "bfs-sis-a" },
    strategy_class: OmniAuth::Strategies::KeycloakOpenId

Unfortunately, the configuration request issued by my application fails, as it is visible in this trace:

Started POST "/users/auth/keycloakopenid" for 127.0.0.1 at 2021-11-22 16:29:35 +0100
D, [2021-11-22T16:29:35.599250 #9892] DEBUG -- omniauth: (keycloakopenid) Going to get Keycloak configuration. URL: https://identity-a.bit.admin.ch/auth/realms/bfs-sis-a/.well-known/openid-configuration
E, [2021-11-22T16:29:35.667631 #9892] ERROR -- omniauth: (keycloakopenid) Keycloak configuration request failed with status: 404. URL: https://identity-a.bit.admin.ch/auth/realms/bfs-sis-a/.well-known/openid-configuration
D, [2021-11-22T16:29:35.667881 #9892] DEBUG -- omniauth: (keycloakopenid) Request phase initiated.

As you can see, the requested URL differs by the "/auth/" part, that seems to belong to the default URL of Keycloak. But our dedicated server's configuration appears to be different.
Reading the code of the gem, I see that this URL element cannot be changed.

Can you please let me know if my analysis is correct, and if we have a solution?
Thanks a lot!

Get Request is now Post Request

Why did the auth/keycloak route switch to post and whats the inteded functionality now? (I upgraded from 1.2 to 1.4)

Maybe I got the concept wrong, but I am using this route with redirect_to, which is not possible now anymore.

Could someone enlighten me?
If there is any demand for that, and I am not the only only one with this issue, I could add it to the readme afterwards.

Thanks a lot for the great gem and the help!

cannot load such file -- /Library/Ruby/Gems/2.6.0/gems/omniauth-keycloak-1.2.0/lib/omniauth-keycloak.rb (LoadError)

Hi!

I have this gem

Using oauth2 1.4.4
Using omniauth 1.9.1
Using omniauth-oauth2 1.6.0
Using omniauth-keycloak 1.2.0

Also I did bundle and bundle install but I get this error:

Traceback (most recent call last):
49: from bin/rails:3:in <main>' 48: from bin/rails:3:in load'
47: from /Users/catalinarojas/portal-backend/bin/spring:15:in <top (required)>' 46: from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in require'
45: from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in require' 44: from /Library/Ruby/Gems/2.6.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in <top (required)>'
43: from /Library/Ruby/Gems/2.6.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in load' 42: from /Library/Ruby/Gems/2.6.0/gems/spring-2.1.0/bin/spring:49:in <top (required)>'
41: from /Library/Ruby/Gems/2.6.0/gems/spring-2.1.0/lib/spring/client.rb:30:in run' 40: from /Library/Ruby/Gems/2.6.0/gems/spring-2.1.0/lib/spring/client/command.rb:7:in call'
39: from /Library/Ruby/Gems/2.6.0/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in call' 38: from /Library/Ruby/Gems/2.6.0/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in load'
37: from /Users/catalinarojas/portal-backend/bin/rails:9:in <top (required)>' 36: from /Library/Ruby/Gems/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in require'
35: from /Library/Ruby/Gems/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in load_dependency' 34: from /Library/Ruby/Gems/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in block in require'
33: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in require' 32: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in require_with_bootsnap_lfi'
31: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in register' 30: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in block in require_with_bootsnap_lfi'
29: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in require' 28: from /Library/Ruby/Gems/2.6.0/gems/railties-5.2.4.3/lib/rails/commands.rb:18:in

'
27: from /Library/Ruby/Gems/2.6.0/gems/railties-5.2.4.3/lib/rails/command.rb:46:in invoke' 26: from /Library/Ruby/Gems/2.6.0/gems/railties-5.2.4.3/lib/rails/command/base.rb:69:in perform'
25: from /Library/Ruby/Gems/2.6.0/gems/thor-1.0.1/lib/thor.rb:392:in dispatch' 24: from /Library/Ruby/Gems/2.6.0/gems/thor-1.0.1/lib/thor/invocation.rb:127:in invoke_command'
23: from /Library/Ruby/Gems/2.6.0/gems/thor-1.0.1/lib/thor/command.rb:27:in run' 22: from /Library/Ruby/Gems/2.6.0/gems/railties-5.2.4.3/lib/rails/commands/server/server_command.rb:142:in perform'
21: from /Library/Ruby/Gems/2.6.0/gems/railties-5.2.4.3/lib/rails/commands/server/server_command.rb:142:in tap' 20: from /Library/Ruby/Gems/2.6.0/gems/railties-5.2.4.3/lib/rails/commands/server/server_command.rb:145:in block in perform'
19: from /Library/Ruby/Gems/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in require' 18: from /Library/Ruby/Gems/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in load_dependency'
17: from /Library/Ruby/Gems/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in block in require' 16: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in require'
15: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in require_with_bootsnap_lfi' 14: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in register'
13: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in block in require_with_bootsnap_lfi' 12: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in require'
11: from /Users/catalinarojas/portal-backend/config/application.rb:7:in <main>' 10: from /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler.rb:114:in require'
9: from /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/runtime.rb:65:in require' 8: from /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/runtime.rb:65:in each'
7: from /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/runtime.rb:76:in block in require' 6: from /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/runtime.rb:76:in each'
5: from /Library/Ruby/Gems/2.6.0/gems/bundler-2.0.2/lib/bundler/runtime.rb:81:in block (2 levels) in require' 4: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in require'
3: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in require_with_bootsnap_lfi' 2: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in register'
1: from /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in block in require_with_bootsnap_lfi' /Library/Ruby/Gems/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in require': cannot load such file -- /Library/Ruby/Gems/2.6.0/gems/omniauth-keycloak-1.2.0/lib/omniauth-keycloak.rb (LoadError)

Keycloak reports incorrect redirect_uri

I am currently trying to get this to work with a fresh Keycloak, and am struggling the the Keycloak configuration.

I suspect I am missing something.

It would be nice to have an example configuration for Keycloak

Error: uninitialized constant OmniAuth::Strategies::KeycloakOpenId::MultiJson after gem update

My pplication is running on Ruby 2.7 / Rails 5.2.4 on Windows 10 and SUSE (SLES 15.SP3).
It uses Devise for authentication, with omniauth-keycloak, which worked fine for several months.

This morning I had tu run "bundle update" for an unrelated subject. After this update, invoking the link to Keycloak remote server for authentication issues the following error:

DEBUG -- omniauth: (keycloakopenid) Going to get Keycloak configuration. URL: https://identity-a.manage.ch/realms/bfs-sis-a/.well-known/openid-configuration
E, [2022-07-19T08:57:43.384693 #13364] ERROR -- omniauth: (keycloakopenid) Authentication failure! uninitialized constant OmniAuth::Strategies::KeycloakOpenId::MultiJson: NameError, uninitialized constant OmniAuth::Strategies::KeycloakOpenId::MultiJson
Processing by Users::OmniauthCallbacksController#failure as HTML

And redirects to the sign-in page.

The OmniAuth::Strategies::KeycloakOpenId::MultiJson constant does not exist in the project indeed.
The closest constant OmniAuth::Strategies::KeycloakOpenId i used in the devise.rb initializer:

  config.omniauth :keycloak_openid,
    "BFS.SIS",
    client_options: { base_url: '', site: Rails.application.credentials.integration[:authentication_url], realm: "bfs-sis-a" },
    strategy_class: OmniAuth::Strategies::KeycloakOpenId

Installed gem versions are:

  • devise (4.8.1)
  • oauth2 (2.0.6)
  • omniauth (2.1.0)
  • omniauth-oauth2 (1.7.3)
  • omniauth-keycloak (1.4.2)
  • omniauth-rails_csrf_protection (1.0.1)

How could I solve this issue?

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.