pallets-eco / flask-security Goto Github PK
View Code? Open in Web Editor NEWQuick and simple security for Flask applications
License: MIT License
Quick and simple security for Flask applications
License: MIT License
we support postgres - lets support mysql, mongo
Today - if a user is deactivated, existing session and API tokens still work. They shouldn't.
We might be able to piggy back on the idea of a generation/change count in the user model.
Tokens and sessions would be tied to a particular generation/count - on password change or deactivate - we would reject the token/session (since the gen/count wouldn't match)
This would also potentially solve the API token password hash every time
see pallets-eco/flask-security-3.0#811
this should speed things up and remove external dependency.
This has caused more folks to abandon flask-security that anything.
pallets-eco/flask-security-3.0#731
cross posted:
https://stackoverflow.com/questions/47157092/slow-flask-response-when-using-flask-security
The issue is that FS is using default hashing per-request to verify that the data in the auth token corresponds to the stored user password.
This seems totally wrong - auth tokens shouldn't be invalidated just because someone changes their password
Also - while the UserMixin has a method for get_auth_token - it doesnt easily allow overriding verifying auth_token - so it is almost impossible to do one's on thing.
Tokens are already signed - that should be enough.
The current password validator is very simple - and not easily overridable - I believe today you would have to extend both forms that has a password and then change the validator.
Should be simpler than that.
consider adding OOB support for https://pypi.org/project/zxcvbn/
Things like how to generate secret keys - that MUST set flask's SECRET_KEY, good signing algorithms, password encryption etc....
sqlalchemy - pool_pre_ping=True
The new 2FA code has a hardwired random secret generator that is used in generating totp_secrets - that doesn't seem secure nor configurable - look into using passlib.totp module
The confirmation token can be used again, even if user already confirmed. Using the token actually logs in the user - so this is a potential security issue.
This is discussed in #54 (upstream #662)
Not clear the any security implications - but requiring the user to relogin after confirmation is a nice option.
The new 2FA feature has basic unit tests - but coverage isn't up to what we need.
According to @baurt this is mostly around testing JSON request/response.
This is just a U2F/WebAuthn support tracking issue for discussion and etc.
I'd love to implement this but I don't yet have time and any U2F hardware, maybe someone else has thought about implementing this?
There are few threads on folks using token authentication to get around difficulty with CSRF and sessions.
Today, flask-security doesn't really support a way for non-template/form UIs to get CSRF token access.
While tokens appear to solve some issues it isn't that secure since today at least, flask and flask-security still create a session and still sets a session cookie.
Flask-Security should at the very least accept POST requests on the /logout endpoint.
My reasoning being:
In my case, I already have a form in the nav, that sends a post request to the /logout endpoint with the submit button, and it's styled to look exactly as any other item on the navbar. Sure, It's not effortless, but I think the work's kinda worth it.
Today, FS only as a single callback: _security._unauthorized_callback
which is used in both the 'not authenticated' @auth_required case as well as the
unauthorized case @role_required.
The first should return a 401 ,
the second a 403 (Forbidden).
Probably the easiest backward compatible thing is to introduce a new unauthenticated_callback and if that is set - call it.
Also - only FlaskLogin's unauthorized handler does all the appropriate things to redirect to a login view - FS's just returns some lame html....
Flask-Admin has a writeup about how to work with Flask-Security - it is old and we now have a base template. However, we should make it REALLY easy and not require to copy all the FS templates to get it to work well.
Then, do a PR for Flask-Admin to get our integration in their docs.
Hello @jwag956 ,
the newly added feature of caching apikeys seems to not work in your fork.
Only the Default Flask security dict is used and configuration from my project's config.py file is not used as Flask-Security does.
I don't know what are the differences between the two projects on this topic but I cannot execute the code of my PR as the option to use cache is not set properly.
The original 2FA PR was done a while ago - and some things have changed, and the naming conventions in the code aren't very consistent. Since this will require some minor backwards incompatibilities, getting them in before the initial release seems prudent:
A few templates and code refer to 'change_method' - but in fact this really is about changing the two factor profile (like phone number, etc). And some, like 'two_factor_change_method_password_confirmation` are just plain silly long names.
Proposal:
leave configuration variables as is - SECURITY_TWO_FACTOR_xxx
Need some additional testing/verification - but it appears that after a successful registration, the JSON response always returns an auth_token. That will avoid the normal login form validation that checks for confirmation.?
Possible 4.0 ideas
Trying here where I might have more chance with an undead fork.
I'm using flask_babelex in my app and the app translation itself works fine, but the flask-security part stays in english.
def create_app():
...
babel = Babel(app) # noqa: F841
...
security = Security( # noqa: F841
app, user_datastore, register_form=ExtendedRegisterForm, confirm_register_form=ExtendedRegisterForm
)
...
@babel.localeselector
def get_locale():
# if a user is logged in, use the locale from the user settings
identity = getattr(g, "identity", None)
if identity is not None and identity.id:
return identity.user.locale
# otherwise try to guess the language from the user accept
# header the browser transmits. We support fr/en in this
# example. The best match wins.
print(f"Best locale choosed: {request.accept_languages.best_match(AVAILABLE_LOCALES)}")
return request.accept_languages.best_match(AVAILABLE_LOCALES)
@babel.timezoneselector
def get_timezone():
identity = getattr(g, "identity", None)
if identity is not None and identity.id:
return identity.user.timezone
My get_locale
returns "fr" as my browser have been set in french, but flask-security form labels and error messages stays in english.
An example is my register form, which have an extra field "username", that field gets translated, the others from flask-security doesn't.
Any ideas on how I can try to debug that ?
More infos : my form is render by render_form
from greyli/bootstrap-flask but since every other parts of the forms, except flask-security works, I don't know if that can be the issue, I do remember seeing issues about translations upstream but nothing seemed to help here.
I think people would appreciate if the changes to the Required
class from flask_security.forms
could be somehow be listed as a breaking change, removed in general (if people should use wtf
's DataRequired
instead) or marked deprecated. Right now using Required()
on a form field causes a generic error like this which is not ideal:
File "~/Project/.venv/lib/python3.7/site-packages/flask_security/forms.py", line 76, in __init__
self._original_message = kwargs["message"]
KeyError: 'message'
For CI - maybe add testing against real DB: postgres, mongo, mysql?
we have a lot of configs - 3 ORMs 3 DBs just for RDBMS - and mongo...
Hi,
Thank for continuing the development!
Each time a user is successfully submitting the second factor, the function "two_factor_token_validation" generates a new "tf_totp_secret" via "complete_two_factor_process".
This means, it is only possible to login once because the user does not know the newly generated "tf_totp_secret" afterwards.
This bug was introduced with the following commit c8d7adb
Now that this package is starting to go more towards a black style, would you be opposed to blackening the entire package? There is a plugin for pytest to use black formatting: https://github.com/shopkeep/pytest-black. This doesn't work with Python 2, which would mean that the Python 2 support would have to be dropped. Not sure your feelings about that since Python 2 is not going to be supported as of 1/1/2020 (https://pythonclock.org/).
Hi,
I want to add a little feature to the library (redirect after sending the passwordless login info), but I'm having troubles setting up a dev and test environment.
I've cloned the repo in a virtualenv and ran pip install -e .[tests]
(I had to ask around on IRC about how to find a way to install and setup the test dependencies).
When running the tests with python setup.py test
I'm getting
1 failed, 66 passed, 690 skipped, 12 warnings
The failure is always the same (also when running a specific test):
================================================================== ERRORS ==================================================================
_________________________________________________ ERROR at setup of test_custom_change_url _________________________________________________
request = <SubRequest 'app' for <Function test_custom_change_url>>
@pytest.fixture()
def app(request):
app = Flask(__name__)
app.response_class = Response
app.debug = True
app.config['SECRET_KEY'] = 'secret'
app.config['TESTING'] = True
app.config['LOGIN_DISABLED'] = False
app.config['WTF_CSRF_ENABLED'] = False
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECURITY_PASSWORD_SALT'] = 'salty'
for opt in ['changeable', 'recoverable', 'registerable',
'trackable', 'passwordless', 'confirmable']:
app.config['SECURITY_' + opt.upper()] = opt in request.keywords
if 'settings' in request.keywords:
> for key, value in request.keywords['settings'].kwargs.items():
E AttributeError: 'bool' object has no attribute 'kwargs'
tests\conftest.py:58: AttributeError
I guess docs on how to contribute would be very helpful, not only for me but also to make it easier for future contributors.
Hi,
Thank for continuing development of Flask-Security: appreciated :-)
I tried to use Flask-Security-Too as a 'drop in' replacement. Forms, templates etc. work, but I think I am running into a problem with postgres UUID fields.
User model:
from sqlalchemy.dialects.postgresql import UUID
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
email = Column(String(255), unique=True)
first_name = Column(String(255), index=True)
last_name = Column(String(255), index=True)
username = Column(String(255), unique=True)
password = Column(String(255))
active = Column(Boolean())
created_at = Column(DateTime, default=datetime.datetime.utcnow)
confirmed_at = Column(DateTime())
roles = relationship('Role', secondary='roles_users', backref=backref('users', lazy='dynamic'))
Console:
sqlalchemy.exc.DataError: (psycopg2.DataError) invalid input syntax for uuid: "[email protected]"
Looks as it doesn't see the UUID pk field correctly?
Configuration:
https://github.com/acidjunk/improviser/blob/flask-security-too/improviser/main.py
Travis CI:
https://travis-ci.com/acidjunk/improviser/jobs/222120290
Or do I miss some new configuration stuff, somewhere?
Today, it is all or nothing - enable ability to have normal login and opt-in per user for 2FA.
It would be great if this could address storing things like totp_secret etc on the session.
That decorator always simply redirects if caller is logged in.
For JSON requests (especially POST) it should return a 400.
Probably need to add a callback so folks can customize the return.
Hi there. I seem to be in the minority, but the password caching mechanism does not work out of the box for me (#91 seems to imply that it will work for some folks). I'm just using flask's built in dev server flask run
The main issue seems to be obvious: The cache is being stored in a werkzeug Local(), which (for me) gets wiped after every request. Thus the cache is working, in a sense, but it gets cleared after every request. This feels correct because the Locals() interface acts as a guard between accidentally sharing data across threads, where what I want is actually a shared cache between all threads. I want all my requests to be fast (and share their cache between all requests).
I fixed this by simply replacing the Locals() call with a globally instantiated call to the TTLCache (see below). I'll probably end up throwing this into Redis (so that I can share caches across like 20 workers).
I've found the password verification to be a substantial bottleneck in my app where polling requests take at a minimum 700ms just to verify the hash (ugh) on every single auth-token request, which really starts to bog everything down if you're trying to stack requests or poll relatively quickly (like every couple seconds). If you've got a handful of requests across a handful of clients, forget about it. Thus I'm very interested in this feature, and a clean implementation that works for me.
So... am I doing something different from everyone else or do I have different requirements?
Before:
local_cache = Local()
# ...
def _request_loader(request):
# ...
use_cache = cv("USE_VERIFY_PASSWORD_CACHE")
if not user:
return _security.login_manager.anonymous_user()
if use_cache:
cache = getattr(local_cache, "verify_hash_cache", None)
if cache is None: # its ALWAYS None!
cache = VerifyHashCache()
local_cache.verify_hash_cache = cache
if cache.has_verify_hash_cache(user):
return user
if verify_hash(data[1], user.password):
cache.set_cache(user)
return user
else:
if verify_hash(data[1], user.password):
return user
After:
#local_cache = Local()
verifyCache = None
# ...
def _request_loader(request):
# ...
use_cache = cv("USE_VERIFY_PASSWORD_CACHE")
if not user:
return _security.login_manager.anonymous_user()
if use_cache:
global verifyCache
if (verifyCache is None):
verifyCache = VerifyHashCache()
if verifyCache.has_verify_hash_cache(user):
return user
if verify_hash(data[1], user.password):
verifyCache.set_cache(user)
return user
else:
if verify_hash(data[1], user.password):
return user
return _security.login_manager.anonymous_user()
Interestingly, this only partially fails on my production setup with gunicorn. But it only works if I refresh the page (and thus do couple of requests back to back). I assume that this is because gunicorn will maybe re-use the thread for requests from the same client within a window (or maybe Chrome is pipelining the http requests over a single socket). However, if I just let it poll every few seconds it consistently misses the cache (presumably because gunicorn is resetting the thread).
I'm curious what setup everyone else is using where this "just works" for them?
itsdangerous allows for many signing algorithms - currently it defaults to SHA1 - which is considered 'broken'. _get_serializer should take new config parameters that allows apps to change this.
The new 2FA code has some issues with views:
doesn't allow overriding names
default names don't conform to stds (underscores)
we should allow BOTH regular AND 2FA logins if an application wants to support them - also make sure it is easy to REQUIRE 2FA.
also - add missing context processor initialization in core.py.
As discussed here:
wtforms/flask-wtf#354
Thanks for taking over the maintenance of Flask-Security!
I tried extending the registration form but I am getting the following error:
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/app.py", line 2328, in __call__
return self.wsgi_app(environ, start_response)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/app.py", line 2314, in wsgi_app
response = self.handle_exception(e)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/app.py", line 1760, in handle_exception
reraise(exc_type, exc_value, tb)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/_compat.py", line 36, in reraise
raise value
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/app.py", line 2311, in wsgi_app
response = self.full_dispatch_request()
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/app.py", line 1834, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/app.py", line 1737, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/_compat.py", line 36, in reraise
raise value
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/app.py", line 1832, in full_dispatch_request
rv = self.dispatch_request()
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/app.py", line 1818, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask_security/decorators.py", line 254, in wrapper
return f(*args, **kwargs)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask_security/views.py", line 185, in register
**_ctx("register")
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask_security/core.py", line 728, in render_template
return render_template(*args, **kwargs)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/templating.py", line 135, in render_template
context, ctx.app)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/flask/templating.py", line 117, in _render
rv = template.render(context)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/jinja2/environment.py", line 1008, in render
return self.environment.handle_exception(exc_info, True)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/jinja2/environment.py", line 780, in handle_exception
reraise(exc_type, exc_value, tb)
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/jinja2/_compat.py", line 37, in reraise
raise value.with_traceback(tb)
File "/home/user/dev/templates/security/register_user.html", line 1, in top-level template code
{% extends "white-base.html" %}
File "/home/user/dev/templates/white-base.html", line 78, in top-level template code
{% block content %}
File "/home/user/dev/templates/security/register_user.html", line 82, in block "content"
{{ render_field_with_errors(register_user_form.first_name,placeholder="First Name",class="input-form-field form-control",spellcheck="false") }}
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/jinja2/runtime.py", line 579, in _invoke
rv = self._func(*arguments)
File "/home/user/dev/templates/security/_macros.html", line 2, in template
{% if field.errors %}
File "/home/user/virtualenv/dev/3.5/lib/python3.5/site-packages/jinja2/environment.py", line 430, in getattr
return getattr(obj, attribute)
jinja2.exceptions.UndefinedError: 'flask_security.forms.ConfirmRegisterForm object' has no attribute 'first_name'
I'm not sure what the issue is as I've made an extended registration form and called it too.
security = Security(app, user_datastore, register_form=ExtendedRegisterForm)
I've seen this issue in the original repo however I'm not sure if it is related or what the fix is.
Any help would be appreciated.
Turns out that if we get a validation error (which are LazyStrings) and are requesting a JSON response, we get a 500 Json encoding error since LazyString isn't json serializable.
Amazingly this wasn't caught in unit tests, because the unit test code actually overrode the JSONEncoder and handles LazyStrings.
sigh.
We know have the concept of want_json() which will look at the Accept header - use that for ALL responses instead of request.is_json().
This will fix strange things like having to send in an empty body in order to get a json response.
Today, there is no way to get information about what the session cookie represents.
This info is returned on login and other APIs - but for UIs, after hitting 'refresh' it is useful to be able to query current status - is a user logged in? what is user id/name/email etc.
For example there's https://github.com/jwag956/flask-security/blob/master/flask_security/script.py , https://github.com/jwag956/flask-security/blob/master/flask_security/datastore.py#L369 , and quite a few NotImplementedException
-s.
I'm sure there's more workarounds for ancient dependency versions but maybe those specific ones could be removed?
I am using Python 3.6.8
in the virtual environment created whilst following the Quick Start. Same thing happens with Python 3.5.3.
This error comes up immediately when attempting to run the app.
Traceback (most recent call last): File "appme.py", line 4, in <module> from flask_security.models import fsqla ModuleNotFoundError: No module named 'flask_security.models'
The appme.py
file is copied verbatim from the Quick Start.
Today, flask-security doesn't allow the application controlling JSON output for flask-security - it should - just as it allows to override render_template.
This would allow applications to have a uniform API.
Given that client certificates are commonly regarded as the best method of verifying if the user is authorized or not it would be really nice if Flask-Security could allow such logins with less setup required.
It's also worth mentioning that client certificates are something that quite a lot of countries provide to their citizens (for ex. Estonia, Latvia, Belgium, Finland) and is very commonly used in at least Estonia so there'd definitely be a relatively large user-base for such integration.
I myself implemented it by reusing Flask-Dance's OAuth link schema and providing a few endpoints to accept client-certificates. It would be much easier if common web-server configuration examples and etc. were provided and the endpoints with necessary checks existed.
I am ready to push 3.2.0 - Tyler - it would be great if you could try 3.2.0rc4 in your application to make sure nothing obvious broke.
I'd like to push this in the next few days....
Thanks!
It would be really nice if code could slowly start utilizing Python's type hints as specified by this PEP to possibly catch a few bugs but just to simplify development when using a good IDE.
Since Flask-SqlAlchemy 2.4.0 it's easy to enable SQL pool pre-pinging that should increase reliability of any services using Flask-Security, it would be a good default in majority of cases. What do you think?
This is how I and some others have done it:
self.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
"pool_pre_ping": True,
}
Today - an auth_token is returned on login and some other endpoints.
This isn't ideal - UIs shouldn't normally need this - they should use sessions. It is an unnecessary security hole to send the auth_token to a browser if they don't need it.
Furthermore - options such as TTL should be specifiable as part of an API (not to exceed global configurations).
Upon thinking about this - considering moving away from current token mechanism completely and implementing the first pieces of oauth2 (client credential and password grant), along with using JWT to represent the access token.
I'm currently trying to get multi-email, better audit logs and password history support into the original Flask-Security but that has proven non-trivial. Are there any plans to make it easier in this fork or even integrate the functionality?
Just that people reading this would understand the feature request better:
I am having issues with the way an unauthenticated request is handled in conjunction with flask-restplus.
Given the following example code
@api.route('/users/<id>', endpoint='user')
class UserAPI(Resource):
@http_auth_required
@permissions_accepted('user_read', 'user_read_all')
@api.marshal_with(user_get)
@api.response(200, "Success")
def get(self, id):
user = do_something_to_get_user()
return user
When I do a GET request on this endpoint I get the following stacktrace (for example by doing this: curl -X GET "http://localhost:5000/api/v1/users/1" -H "accept: application/json"
):
[2019-08-16 17:08:27,192] ERROR in app: Exception on /api/v1/users/1 [GET]
Traceback (most recent call last):
File "/home/mkoek/prog/taskmanager/venv/taskmanager/lib/python3.6/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/home/mkoek/prog/taskmanager/venv/taskmanager/lib/python3.6/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/mkoek/prog/taskmanager/venv/taskmanager/lib/python3.6/site-packages/flask_restplus/api.py", line 330, in wrapper
return self.make_response(data, code, headers=headers)
File "/home/mkoek/prog/taskmanager/venv/taskmanager/lib/python3.6/site-packages/flask_restplus/api.py", line 351, in make_response
resp = self.representations[mediatype](data, *args, **kwargs)
File "/home/mkoek/prog/taskmanager/venv/taskmanager/lib/python3.6/site-packages/flask_restplus/representations.py", line 25, in output_json
dumped = dumps(data, **settings) + "\n"
File "/usr/lib/python3.6/json/__init__.py", line 238, in dumps
**kw).encode(obj)
File "/usr/lib/python3.6/json/encoder.py", line 201, in encode
chunks = list(chunks)
File "/usr/lib/python3.6/json/encoder.py", line 437, in _iterencode
o = _default(o)
File "/usr/lib/python3.6/json/encoder.py", line 180, in default
o.__class__.__name__)
TypeError: Object of type 'Response' is not JSON serializable
I think what we see is happening here is, that _security._render_json (at https://github.com/jwag956/flask-security/blob/master/flask_security/decorators.py#L75) returns the following tuple down the decorator line, in this case it is this:
(<Response 81 bytes [200 OK]>, 401, {'WWW-Authenticate': 'Basic realm="Taskmanager Login Required"'})
In the case the accept: application/json
is set, flask-restplus expects either a Response object or a sequence that should be serialized to json. In this case it received a sequence with a response object in it and got confused ;)
I think in this case (the want_json path) it is good idea to just pass a Response object back with the correct body, headers and response code. This gives the least chances of breakage when this extension is used together with other flask extensions.
Also when having a look inside _security._render_json -> views.default_render_json (at https://github.com/jwag956/flask-security/blob/master/flask_security/views.py#L130)
I am preparing a pull request, but I wanted to make sure to be on the page on beforehand. Is it OK if I implement in default_render_json to only return a Response object or maybe create a specific method for the want_json case? Should a 401 response have an empty body or should there be a message saying that it was an unauthenticated request?
First of all, awesome of you @jwag956 to take this library into your hands! I hope I'll be able to contribute meaningfully to it.
I'm using passwordless login in one of my projects, but I've been having issues with email deliverability. I'd like to be able to add username/password login too, but currently there's only one method allowed per app (based on the config settings). So the ability to set multiple login methods would be helpful in my case.
I understand this suggestion might be a difficult one to implement, so please don't see this as an "I need this asap"-thing. I just wanted to log it here because I'm probably not the only one with this suggestion and open up the discussion.
Please see: pallets-eco/flask-security-3.0#801
turns out that our validators have a similar issue with form labels - they are evaluated too early (at import time). validators actually have state - self.message - that is shared among all instances of a form (or in our case - the entire app in the case of 'email_required').
These messages need to be lazy_strings so that they are evaluated in the context of the request.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.