Skip to content
This repository has been archived by the owner on Mar 15, 2018. It is now read-only.

Commit

Permalink
Merge pull request #3537 from diox/remove-iarc-waffle
Browse files Browse the repository at this point in the history
 Remove old IARC implementation
  • Loading branch information
diox committed Sep 14, 2016
2 parents 203f2e8 + 1e1e90b commit d2e36d2
Show file tree
Hide file tree
Showing 48 changed files with 935 additions and 2,803 deletions.
1 change: 0 additions & 1 deletion lib/iarc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
import client # noqa
import utils # noqa
344 changes: 197 additions & 147 deletions lib/iarc/client.py
Original file line number Diff line number Diff line change
@@ -1,162 +1,212 @@
# -*- coding: utf8 -*-
import base64
import functools
import os
import datetime
import requests
from urlparse import urljoin
from uuid import UUID

from django.conf import settings
from django.db import transaction

import commonware.log
from django_statsd.clients import statsd
from suds import client as sudsclient
from post_request_task.task import task

from lib.iarc.serializers import IARCRatingListSerializer
from mkt.site.helpers import absolutify
from mkt.translations.utils import no_translation
from mkt.users.models import UserProfile

log = commonware.log.getLogger('z.iarc')


root = os.path.join(settings.ROOT, 'lib', 'iarc', 'wsdl', settings.IARC_ENV)
wsdl = {
'services': 'file://' + os.path.join(root, 'iarc_services.wsdl'),
}

# Add a list of supported methods here.
services = ['Get_App_Info', 'Set_Storefront_Data', 'Get_Rating_Changes']


class Client(object):
"""
IARC SOAP client.
A wrapper around suds to make calls to IARC, leaving room for future WSDL
expansion. Example usage::
client = Client('services')
response = client.Get_App_Info(XMLString=xml)
print response # response is already base64 decoded.
class IARCException(Exception):
pass


def _iarc_headers():
"""HTTP headers to include in each request to IARC."""
return {
'StoreID': settings.IARC_V2_STORE_ID,
'StorePassword': settings.IARC_V2_STORE_PASSWORD,
}


def _iarc_request(endpoint_name, data):
"""Wrapper around requests.post which handles url generation from the
endpoint_name and auth headers. Returns data as a dict."""
url = urljoin(settings.IARC_V2_SERVICE_ENDPOINT, endpoint_name)
headers = _iarc_headers()
response = requests.post(url, headers=headers, json=data)
try:
value = response.json()
except ValueError:
value = {}
return value


def _iarc_app_data(app):
"""App data that IARC needs in PushCert response / AttachToCert request."""
from mkt.webapps.models import Webapp

author = app.listed_authors[0] if app.listed_authors else UserProfile()
with no_translation(app.default_locale):
app_name = unicode(Webapp.with_deleted.get(pk=app.pk).name)
data = {
'Publish': app.is_public(),
'ProductName': app_name,
'StoreProductID': app.guid,
'StoreProductURL': absolutify(app.get_url_path()),
# We want an identifier that does not change when users attached to
# an app are shuffled around, so just use the app PK as developer id.
'StoreDeveloperID': app.pk,
# PushCert and AttachToCert docs use a different property for the
# developer email address, use both just in case.
'DeveloperEmail': author.email,
'EmailAddress': author.email,
'CompanyName': app.developer_name,
}
return data


def get_rating_changes(date=None):
"""
Call GetRatingChange to get all changes from IARC within 24 hours of the
specified date (using today by default), and apply them to the
corresponding Webapps.
def __init__(self, wsdl_name):
self.wsdl_name = wsdl_name
self.client = None

def __getattr__(self, attr):
for name, methods in [('services', services)]:
if attr in methods:
return functools.partial(self.call, attr, wsdl=name)
raise AttributeError('Unknown request: %s' % attr)

def call(self, name, **data):
log.info('IARC client call: {0} from wsdl: {1}'.format(name, wsdl))

if self.client is None:
self.client = sudsclient.Client(wsdl[self.wsdl_name], cache=None)

# IARC requires messages be base64 encoded and base64 requires
# byte-strings.
for k, v in data.items():
if isinstance(v, unicode):
# Encode it as a byte-string.
v = v.encode('utf-8')
data[k] = base64.b64encode(v)

with statsd.timer('mkt.iarc.request.%s' % name.lower()):
response = getattr(self.client.service, name)(**data)

return base64.b64decode(response)


class MockClient(Client):
FIXME: Could add support for pagination, but very low priority since we
will never ever get anywhere close to 500 rating changes in a single day.
"""
Mocked IARC SOAP client.
"""

def call(self, name, **data):
responses = {
'Get_App_Info': MOCK_GET_APP_INFO,
'Set_Storefront_Data': MOCK_SET_STOREFRONT_DATA,
'Get_Rating_Changes': MOCK_GET_RATING_CHANGES,
}

return responses.get(name, '')


def get_iarc_client(wsdl):
from mkt.webapps.models import IARCCert

# EndDate should be the most recent one.
end_date = date or datetime.datetime.utcnow()
start_date = end_date - datetime.timedelta(days=1)
log.info('Calling IARC GetRatingChanges from %s to %s',
start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'))
data = _iarc_request('GetRatingChanges', {
'StartDate': start_date.strftime('%Y-%m-%d'),
'EndDate': end_date.strftime('%Y-%m-%d'),
'MaxRows': 500, # Limit.
'StartRowIndex': 0 # Offset.
})
for row in data.get('CertList', []):
# Find app through Cert ID, ignoring unknown certs.
try:
cert = IARCCert.objects.get(cert_id=UUID(row['CertID']).get_hex())
except IARCCert.DoesNotExist:
continue
serializer = IARCRatingListSerializer(instance=cert.app, data=row)
if serializer.is_valid():
rating_bodies = [body.id for body
in serializer.validated_data['ratings'].keys()]
serializer.save(rating_bodies=rating_bodies)
return data


@transaction.atomic
def search_and_attach_cert(app, cert_id):
"""Call SearchCerts to get all info about an existing cert from IARC and
apply that info to the Webapp instance passed. Then, call AttachToCert
to notify IARC that we're attaching the cert to that Webapp."""
serializer = search_cert(app, cert_id)
if serializer.is_valid():
serializer.save()
else:
raise IARCException('SearchCerts failed, invalid data!')
data = _attach_to_cert(app, cert_id)
if data.get('ResultCode') != 'Success':
# If AttachToCert failed, we need to rollback the save we did earlier,
# we raise an exception to do that since we are in an @atomic block.
raise IARCException(data.get('ErrorMessage', 'AttachToCert failed!'))
return data


def search_cert(app, cert_id):
"""Ask IARC for information about a cert."""
log.info('Calling IARC SearchCert for app %s, cert %s',
app.pk, unicode(UUID(cert_id)))
data = _iarc_request('SearchCerts', {'CertID': unicode(UUID(cert_id))})
# We don't care about MatchFound, serializer won't find the right fields
# if no match is found.
serializer = IARCRatingListSerializer(instance=app, data=data)
return serializer


def _attach_to_cert(app, cert_id):
"""Tell IARC to attach a cert to an app."""
data = _iarc_app_data(app)
data['CertID'] = unicode(UUID(cert_id))
log.info('Calling IARC AttachToCert for app %s, cert %s',
app.pk, data['CertID'])
return _iarc_request('AttachToCert', data)


@task
def publish(app_id):
"""Delayed task to tell IARC we published an app."""
from mkt.webapps.models import IARCCert, Webapp

try:
app = Webapp.with_deleted.get(pk=app_id)
except Webapp.DoesNotExist:
return
try:
cert_id = app.iarc_cert.cert_id
data = _update_certs(app_id, cert_id, 'Publish')
except IARCCert.DoesNotExist:
data = None
return data


@task
def unpublish(app_id):
"""Delayed task to tell IARC we unpublished an app."""
from mkt.webapps.models import IARCCert, Webapp

try:
app = Webapp.with_deleted.get(pk=app_id)
except Webapp.DoesNotExist:
return
try:
cert_id = app.iarc_cert.cert_id
data = _update_certs(app_id, cert_id, 'Unpublish')
except IARCCert.DoesNotExist:
data = None
return data


def refresh(app):
"""Refresh an app IARC information by asking IARC about its certificate."""
serializer = search_cert(app, app.iarc_cert.cert_id)
serializer.save()


# FIXME: implement UpdateStoreAttributes for when the app developer email
# changes.


def _update_certs(app_id, cert_id, action):
"""
Use this to get the right client and communicate with IARC.
UpdateCerts to tell IARC when we publish or unpublish a product.
Endpoint can handle batch updates, but we only need one at a time.
Arguments:
cert_id -- Globally unique ID for certificate.
action -- One of [InvalidateCert, Unpublish, UpdateStoreAttributes,
Publish].
Return:
Update object.
ResultCode (string) -- Success or Failure
ErrorId (string) -- Can pass on to IARC for debugging.
ErrorMessage (string) -- Human-readable error message
"""
if settings.IARC_MOCK:
return MockClient(wsdl)
return Client(wsdl)


MOCK_GET_APP_INFO = '''<?xml version="1.0" encoding="utf-16"?>
<WEBSERVICE xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SERVICE_NAME="GET_APP_INFO" TYPE="RESPONSE">
<ROW>
<FIELD NAME="rowId" TYPE="int" VALUE="1" />
<FIELD NAME="submission_id" TYPE="string" VALUE="52" />
<FIELD NAME="title" TYPE="string" VALUE="Twitter" />
<FIELD NAME="company" TYPE="string" VALUE="Mozilla" />
<FIELD NAME="platform" TYPE="string" VALUE="Firefox" />
<FIELD NAME="rating_PEGI" TYPE="string" VALUE="16+" />
<FIELD NAME="descriptors_PEGI" TYPE="string" VALUE="Language, Online" />
<FIELD NAME="rating_USK" TYPE="string" VALUE="Rating Refused" />
<FIELD NAME="descriptors_USK" TYPE="string" VALUE="Explizite Sprache" />
<FIELD NAME="rating_ESRB" TYPE="string" VALUE="Mature 17+" />
<FIELD NAME="descriptors_ESRB" TYPE="string" VALUE="Strong Language" />
<FIELD NAME="rating_CLASSIND" TYPE="string" VALUE="14+" />
<FIELD NAME="descriptors_CLASSIND" TYPE="string" VALUE="Linguagem Impr\xc3\xb3pria" />
<FIELD NAME="rating_Generic" TYPE="string" VALUE="16+" />
<FIELD NAME="descriptors_Generic" TYPE="string" VALUE="" />
<FIELD NAME="storefront" TYPE="string" VALUE="Mozilla" />
<FIELD NAME="interactive_elements" TYPE="string" VALUE="Shares Info, Shares Location, Digital Purchases, Users Interact, " />
</ROW>
</WEBSERVICE>
''' # noqa


MOCK_SET_STOREFRONT_DATA = '''<?xml version="1.0" encoding="utf-16"?>
<WEBSERVICE xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SERVICE_NAME="SET_STOREFRONT_DATA" TYPE="RESPONSE">
<ROW>
<FIELD NAME="action_status" TYPE="string" VALUE="OK - Storefront data Updated" />
<FIELD NAME="submission_id" TYPE="string" VALUE="52" />
<FIELD NAME="security_code" TYPE="string" VALUE="FZ32CU8" />
<FIELD NAME="title" TYPE="string" VALUE="Twitter" />
<FIELD NAME="company" TYPE="string" VALUE="Mozilla" />
<FIELD NAME="rating" TYPE="string" VALUE="16+" />
<FIELD NAME="descriptors" TYPE="string" VALUE="Language, Online" />
<FIELD NAME="interactive_elements" TYPE="string" VALUE="Shares Info, Shares Location, Digital Purchases, Users Interact, " />
</ROW>
</WEBSERVICE>
''' # noqa


MOCK_GET_RATING_CHANGES = '''<?xml version="1.0" encoding="utf-16"?>
<WEBSERVICE xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" SERVICE_NAME="GET_RATING_CHANGES" TYPE="RESPONSE">
<ROW>
<FIELD NAME="rowId" TYPE="int" VALUE="1" />
<FIELD NAME="change_date" TYPE="string" VALUE="11/12/2013" />
<FIELD NAME="submission_id" TYPE="string" VALUE="52" />
<FIELD NAME="security_code" TYPE="string" VALUE="FZ32CU8" />
<FIELD NAME="title" TYPE="string" VALUE="Twitter" />
<FIELD NAME="company" TYPE="string" VALUE="Mozilla" />
<FIELD NAME="email" TYPE="string" VALUE="nobody@mozilla.com" />
<FIELD NAME="new_rating" TYPE="string" VALUE="14+" />
<FIELD NAME="new_descriptors" TYPE="string" VALUE="Linguagem Impr\xc3\xb3pria" />
<FIELD NAME="rating_system" TYPE="string" VALUE="CLASSIND" />
<FIELD NAME="change_reason" TYPE="string" VALUE="Significant issues found in special mission cut scenes." />
</ROW>
<ROW>
<FIELD NAME="rowId" TYPE="int" VALUE="2" />
<FIELD NAME="change_date" TYPE="string" VALUE="11/12/2013" />
<FIELD NAME="submission_id" TYPE="string" VALUE="68" />
<FIELD NAME="security_code" TYPE="string" VALUE="GZ32CU8" />
<FIELD NAME="title" TYPE="string" VALUE="Other App" />
<FIELD NAME="company" TYPE="string" VALUE="Mozilla" />
<FIELD NAME="email" TYPE="string" VALUE="nobody@mozilla.com" />
<FIELD NAME="new_rating" TYPE="string" VALUE="Rating Refused" />
<FIELD NAME="new_descriptors" TYPE="string" VALUE="Explizite Sprache" />
<FIELD NAME="rating_system" TYPE="string" VALUE="USK" />
<FIELD NAME="change_reason" TYPE="string" VALUE="Discrimination found to be within German law." />
</ROW>
</WEBSERVICE>
''' # noqa
data = {
'UpdateList': [{
'CertID': unicode(UUID(cert_id)),
'Action': action,
}]
}
log.info('Calling IARC UpdateCert %s for app %s, cert %s',
action, app_id, data['UpdateList'][0]['CertID'])
return _iarc_request('UpdateCerts', data)
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion lib/iarc_v2/serializers.py → lib/iarc/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
log = commonware.log.getLogger('lib.iarc.serializers')


class IARCV2RatingListSerializer(object):
class IARCRatingListSerializer(object):
"""Class that is used to de-serialize json sent/received from IARC to our
ratings/descriptors/interactive model instances for a given app.
Expand Down
10 changes: 0 additions & 10 deletions lib/iarc/templates/get_app_info.xml

This file was deleted.

8 changes: 0 additions & 8 deletions lib/iarc/templates/get_rating_changes.xml

This file was deleted.

Loading

0 comments on commit d2e36d2

Please sign in to comment.