List all the Users Registered on SUSI.AI

In this blog, I’ll be telling on how SUSI admins can access list of all the registered users from SUSI-server. Following this, they may modify/edit user role of any registered user.

What is User Role?

A UserRole defines the servlet access right. Not all users are allowed to access all the data and services. For  example, To list all the users, minimal user role expected is ADMIN. This classification of users are inspired by the wikipedia User Access Levels, see https://en.wikipedia.org/wiki/Wikipedia:User_access_levels.While querying SUSI, Users are classified into 7 different categories, namely :

  • BOT
  • ANONYMOUS
  • USER  
  • REVIEWER
  • ACCOUNTCREATOR
  • ADMIN
  • BUREAUCRAT

* Please see that these are as of the date of publish of this blog. These are subject to change, which is very unlikely.

All the users who are not logged in but interacting with SUSI are anonymous users. These are only subject to chat with SUSI, login, signup or may use forgot password service. Once a user login to the server, a token is generated and sent back to client to maintain the identity, hence acknowledging them. Privileged users are those who have special rights with them. These are more like moderators with much special rights than any other user. At the top level of the hierarchy are the admins. These users have more rights than anyone. They can change role of any other user, override decision of any privileged user as well.

Let us now look at the control flow of this.

First things first, make a component of User List in the project. Let us name it ListUsers and since it has to be accessible by those users who possess ADMIN rights, you will find it enclosed in Admin package in components folder. Open up

index.js file, import Listusers component  and add route to it in the following way :

...//other import statements
import ListUser from "./components/Admin/ListUser/ListUser";
...//class definition and other methods
<Route path="/listUser" component={ListUser}/>
//other routes defined

Find a suitable image for “List Users” option and add the option for List Users in static appbar component along with the image. We have used Material UI’s List image in our project.

...// other imports

import List from 'material-ui/svg-icons/action/list';

Class and method definition

<MenuItem primaryText="List Users"
          onTouchTap={this.handleClose}
          containerElement={<Link to="/listUser" />}
                rightIcon={<List/>}
      />

...//other options in top right corner menu

Above code snippet will add an option to redirect admins to ‘/listUsers’ route. Let us now have a closer look at functionality of both client and server. By now you must have known what ComponentDidMount does. {If not, I’ll tell you. This is a method which is given first execution after the page is rendered. For more information, visit this link}. As mentioned earlier as well that this list will be available only for admins and may be even extended for privileged users but not for anonymous or any other user, an AJAX call is made to server in ComponentDidMount of ‘listuser’ route which returns the base user role of current user. If user is an Admin, another method, fetchUsers() is called.

let url;
        url = "http://api.susi.ai/aaa/account-permissions.json";
        $.ajax({
            url: url,
            dataType: 'jsonp',
            jsonpCallback: 'py',
            jsonp: 'callback',
            crossDomain: true,
            success: function (response) {
                console.log(response.userRole)
                if (response.userRole !== "admin") {
                    console.log("Not an admin")
                } else {
                    this.fetchUsers();
                    console.log("Admin")
                }
            }.bind(this),
});

In fetchUsers method, an AJAX call is made to server which returns username in JSONArray. The response looks something likes this :

{
	"users" : {
		"email:""test@test.com",
...
},
"Username":["test@test.com", "test@anothertest.com"...]
}

Now, only rendering this data in a systematic form is left. To give it a proper look, we have used material-ui’s table. Import Table, TableBody, TableHeader,

   TableHeaderColumn, TableRow, TableRowColumn from material-ui/table.

In fetchUsers method, response is catched in data Oblect. Now the keys are extracted from the JSON response and mapped with an array. Iterating through array received as username array, we get list of all the registered users. Now, popuulate the data in the table you generated.

return (
                        <TableRow key={i}>
                            <TableRowColumn>{++i}>
                            <TableRowColumn>{name}</TableRowColumn>
                            <TableRowColumn> </TableRowColumn>
                            <TableRowColumn> </TableRowColumn>
                            <TableRowColumn> </TableRowColumn>
                            <TableRowColumn> </TableRowColumn>
                        </TableRow>
                    )

Above piece of code may help you while populating the table. These details are returned from susi server which gets a list of all the registered in the following manner. First, it checks if base url of this user is something apart from admin. If not, it returns error which may look like this :

Failed to load resource: the server responded with a status of 401 (Base user role not sufficient. Your base user role is 'ANONYMOUS', your user role is 'anonymous')

Otherwise, it will generate a client identity, use to to get an authorization object which will loop through authorization.json file and return all the users encoded as JSONArray.

Additional Resources

  1. Official Material UI Documentation on Tables from marterial-ui
  2. Answer by Marco Bonelli on Stackoverflow on How to map JSON response in JavaScript?
  3. Answer by janpieter_z on Stackoverflow – on Render JSON data in ReactJS table
Continue ReadingList all the Users Registered on SUSI.AI

SUSI.AI User Roles and How to Modify Them

In this blog, I discuss what is ‘user-role’ in SUSI.AI, what are the various roles and how SUSI admins can modify/update a user’s roles.

What is User Role?

A UserRole defines the servlet access right. Not all users are allowed to access all the data and services. For  example, To list all the users, minimal user role expected is ADMIN. This classification of users are inspired by the wikipedia User Access Levels, see https://en.wikipedia.org/wiki/Wikipedia:User_access_levels.While querying SUSI, Users are classified into 7 different categories, namely :

  • BOT
  • ANONYMOUS
  • USER  
  • REVIEWER
  • ACCOUNTCREATOR
  • ADMIN
  • BUREAUCRAT

* Please see that these are as of the date of publish of this blog. These are subject to change, which is very unlikely.

If SUSI is active as a bot on some bot integrated platform (like line or kik), the user role assigned to it will be that of BOT. This user role just has technical access to the server.

All the users who are not logged in but interacting with SUSI are ANONYMOUS users. These are only subject to chat, login and signup. They may use forgot password service and reset password services as well.

Once a user login to the server, a token is generated and sent back to client to maintain the identity, hence acknowledging them as USER(s).

Users with role assigned as “REVIEWERS” are expected to manage the Skill CMS. There might be some dispute or conflict in a skill. REVIEWERS then take the access of skill data and finalise the conflict there itself for smooth functionality.

ADMIN users are those who have special rights with them. These are more like moderators with much special rights than any other user.

At the top level of the hierarchy are the BUREAUCRATS. These users have more rights than anyone. They can change role of any other user, override decision of any ADMIN user as well. Both admins and bureaucrats have the access to all the settings file on the server. They not only can look at the list, but also download and upload them. Now these users also have right to upgrade or downgrade any other user as well.

All these user roles are defined in UserRole.java file.

In each request received by the server, the user role of user making the request is compared with the minimal user role in getMinimalUserRole() method. This method is defined in AbstractAPIHandler which validates if a user is allowed to access a particular servlet or not.

private void process(HttpServletRequest request, HttpServletResponse response, Query query) throws ServletException, IOException {
	// object initialisation and comparsions
// user authorization: we use the identification of the user to get the assigned authorization
        Authorization authorization = DAO.getAuthorization(identity);

        if (authorization.getUserRole().ordinal() < minimalUserRole.ordinal()) {
        	response.sendError(401, "Base user role not sufficient. Your base user role is '" + authorization.getUserRole().name() + "', your user role is '" + authorization.getUserRole().getName() + "'");
			return;
        }
// evaluations based on other request parameters.
}

Now that we know about what User Roles actually are, let us look at how the servlet which allows the users {with at least ADMIN login} to change user role of some other user works.

In the request, 2 parameters are expected. These are :

  • user : email id of the user whose role has to be changed.
  • role : new role which will be assigned to this user.

Using a switch case, we identify the user role which is requested. If role is found to be null or any other value apart from “bot”, “anonymous”, “user”, “reviewer”, “accountcreator”, “admin” or “bureaucrat”, an error with error code 400 and error message “Bad User role” is thrown.

In the next steps, server generates client identity in order to get the corresponding Authorization object. If the user is not found in the database, again an error is thrown with error code 400 and error message “role not found

ClientCredential credential = new ClientCredential(ClientCredential.Type.passwd_login, userTobeUpgraded);
            ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, credential.getName());
            if (!DAO.hasAuthorization(identity)) {
                throw new APIException(400, "Username not found");
            }

By now, server is clear with the user identity and new role to be assigned. Since the user role is defined in authorization.json file, we overwrite the existing user role and finally server sends back the new user role of the use

Authorization auth = DAO.getAuthorization(identity);
            try {
                auth.setUserRole(userRole);
            } catch (IllegalArgumentException e) {
                throw new APIException(400, "role not found");
            }

            // Print Response
            result.put("newDetails", auth.getJSON());
            result.put("accepted", true);
            result.put("message", "User role changed successfully!!");
            return new ServiceResponse(result);

 

Continue ReadingSUSI.AI User Roles and How to Modify Them

Making Skill Display Cards Identical in SUSI.AI Skill CMS

SUSI.AI Skill CMS shows all the skills of SUSI.AI. The cards used to display all the skills follow flexbox structure and adjust their height according to content. This lead to cards of different sizes and this needed to be fixed. This needed to fix as the cards looked like this:

The cards display following things:

  • Image related to skill
  • An example query related to skill in double quotes
  • Name of skill
  • Short description of skill

Now to get all these, we make an ajax call to the following endpoint:

http://api.susi.ai/cms/getSkillList.json?model='+ this.state.modelValue + '&group=' + this.state.groupValue + '&language=' + this.state.languageValue

Explanation:

  • this.state.modelValue: This is the model of the skill, stored in state of component
  • this.state.groupValue: This represents the group to which skill belongs to. For example Knowledge, Communication, Music, and Audio, etc.
  • this.state.languageValue: This represents the ISO language code of language in which skill is defined

Now the response is in JSONP format and it looks like:

Now we parse the response to get the information needed and return the following Card(Material UI Component):

<Link key={el}
     to={{
        pathname: '/' + self.state.groupValue + '/' + el + '/' + self.state.languageValue,
            state: {
                        url: url,
                        element: el,
                        name: el,
                        modelValue: self.state.modelValue,
                        groupValue: self.state.groupValue,
                        languageValue: self.state.languageValue,
                       }
           }}>
           <Card style={styles.row} key={el}>
                <div style={styles.right} key={el}>
                       {image ? <div style={styles.imageContainer}>
                        <img alt={skill_name}
                          src={image}
                          style={styles.image} />
                          </div> :
                         <CircleImage name={el} size='48' />}
                             <div style={styles.titleStyle}>{examples}</div>
                             </div>
                             <div style={styles.details}>
                                 <h3 style={styles.name}>{skill_name}</h3>
                                 <p style={styles.description}>{description}</p>
                             </div>
         </Card>
</Link>

Now the information that leads to non-uniformity in these cards is the skill description. Now to solve this we decided to put a certain limit to the description length and if that limit is crossed, then we will show the following dots: “”. The height and width of the cards were fixed according to screen size and we modified the description as follows:

if (skill.descriptions) {
      if (skill.descriptions.length > 120) {
          description = skill.descriptions.substring(0, 119) + '...';
      }
      else {
          description = skill.descriptions;
      }
}

This way no content was being cut and all the skill cards looks identical:

Resources:

Continue ReadingMaking Skill Display Cards Identical in SUSI.AI Skill CMS

Implementing Author’s Skill Page in SUSI.AI CMS

SUSI.AI Skill CMS is improving every day and we keep adding new features to it. Recently a feature was added to display all the skills by an author. This feature only showed the list of skills. The user might want to visit the skill page to see the description so we linked the skills on the list to skill page. The list looked like this:

We need to link skill name and image to respective skill page. Now since this is react based app, we do not have different URL for different skills due to SPA. The description, images and other relevant details of skills were being passed as props. We needed to have routes through which we can directly access the skill. This was done by implementing child routes for Skill CMS. Earlier the description, images, and other relevant data was being passed as props from the BrowseSkill component, but now we need to derive this from the URL:

let baseUrl = 'http://api.susi.ai/cms/getSkillMetadata.json';           
let modelValue = "general";
this.name = this.props.location.pathname.split('/')[2];
this.groupValue = this.props.location.pathname.split('/')[1];
this.languageValue = this.props.location.pathname.split('/')[3];
url = baseUrl + '?model=' + modelValue + '&group=' + this.groupValue +        '&language=' + this.languageValue + '&skill=' + this.name;

We now make an ajax call to this URL for fetching the data:

$.ajax({
               url: url,
               jsonpCallback: 'pc',
               dataType: 'jsonp',
               jsonp: 'callback',
               crossDomain: true,
               success: function (data) {
                   self.updateData(data.skill_metadata)
               }
           });

This updates the skill page with the description, image, author and other relevant details of the skills. Now all left to do is link the skills on the list to their respective links. This is done by following code:

We define skillURL as:

let skillURL = 'http://skills.susi.ai/' + parse[6] + '/' + parse[8].split('.')[0] + '/' + parse[7];

Here parse is an array which contains model, group and ISO language code of the skill. We updated the Image and text component as:

<a
   href={skillURL} >
   <Img
     style={imageStyle}
     src={[
           image1,
           image2
          ]}
     unloader={<CircleImage name={name} size="40"/>}
                          />
</a>
<a
   href={skillURL}
   className="effect-underline" >
    {name}
</a>

Now after proper styling, we had the following looking skill list by author:

Resources

Continue ReadingImplementing Author’s Skill Page in SUSI.AI CMS

Implementing Skill Detail Section in SUSI Android App

SUSI Skills are rules that are defined in SUSI Skill Data repo which are basically the responses SUSI gives to the user queries. When a user queries something from the SUSI Android app, a query to SUSI Server is made which further fetches response from SUSI Skill Data and gives the response to the app. Similarly, when we need to list all skills, an API call is made to server to list all skills. The server then checks the SUSI Skill Data repo for the skills and then return all the required information to the app. Then the app displays all the information about the skill to user. User then can view details of each skill and then interact on the chat interface to use that skill. This process is similar to what SUSI Skill CMS does. The CMS is a skill wiki like interface to view all skills and then edit them. Though the app can not be currently used to edit the skills but it can be used to view them and try them on the chat interface.

API Information

For listing SUSI Skill groups, we have to call on /cms/getGroups.json

This will give you all groups in SUSI model in which skills are present. Current response:

{
  "session": {"identity": {
    "type": "host",
    "name": "14.139.194.24",
    "anonymous": true
  }},
  "accepted": true,
  "groups": [
    "Small Talk",
    "Entertainment",
    "Problem Solving",
    "Knowledge",
    "Assistants",
    "Shopping"
  ],
  "message": "Success: Fetched group list"
}

So, the groups object gives all the groups in which SUSI Skills are located.

Next comes, fetching of skills. For that the endpoint is /cms/getGroups.json?group=GROUP_NAME

Since we want all skills to be fetched, we call this api for every group. So, for example we will be calling http://api.susi.ai/cms/getSkillList.json?group=Entertainment for getting all skills in group “Entertainment”. Similarly for other groups as well.

Sample response of skill:

{
  "accepted": true,
  "model": "general",
  "group": "Shopping",
  "language": "en",
  "skills": {"amazon_shopping": {
    "image": "images/amazon_shopping.png",
    "author_url": "https://github.com/meriki",
    "examples": ["Buy a dress"],
    "developer_privacy_policy": null,
    "author": "Y S Ramya",
    "skill_name": "Shop At Amazon",
    "dynamic_content": true,
    "terms_of_use": null,
    "descriptions": "Searches items on Amazon.com for shopping",
    "skill_rating": null
  }},
  "message": "Success: Fetched skill list",
  "session": {"identity": {
    "type": "host",
    "name": "14.139.194.24",
    "anonymous": true
  }}
}

It gives all details about skills:

  1. image
  2. author_url
  3. examples
  4. developer_privacy_policy
  5. author
  6. skill_name
  7. dynamic_content
  8. terms_of_use
  9. descriptions
  10. skill_rating

Implementation in SUSI Android App

Skill Detail Section UI of Google Assistant

Skill Detail Section UI of SUSI SKill CMS

Skill Detail Section UI of SUSI Android App

The UI of skill detail section in SUSI Android App is the mixture of UI of Skill detail section in Google Assistant ap and SUSI Skill CMS. It displays details of skills in a beautiful manner with horizontal recyclerview used to display the examples.

So, we have to display following details about the skill in Skill Detail Section:

  1. Skill Name
  2. Author Name
  3. Skill Image
  4. Try it Button
  5. Description
  6. Examples
  7. Rating
  8. Content type (Dynamic/Static)
  9. Terms of Use
  10. Developer’s Privacy policy

Let’s see the implementation.

1. Whenever a skill Card View is clicked, showSkillDetailFragment() is called and it opens a new instance of a fragment named SkillDetailsFragment which shows details of the skill. We have to provide necessary information while starting the fragment. This information is passed as a Serializable.

fun showSkillDetailFragment(skillData: SkillData, skillGroup: String) {
   val skillDetailsFragment = SkillDetailsFragment.newInstance(skillData,skillGroup)
   (context as SkillsActivity).fragmentManager.beginTransaction()
           .replace(R.id.fragment_container, skillDetailsFragment)
           .commit()
}

2.  The data which was passed as a Serializeable object is now casted back to the required form and a method to set up the UI is called.

companion object {
   val SKILL_KEY = "skill_key"
   val SKILL_GROUP = "skill_group"
   fun newInstance(skillData: SkillData, skillGroup: String): SkillDetailsFragment {
       val fragment = SkillDetailsFragment()
       val bundle = Bundle()
       bundle.putSerializable(SKILL_KEY, skillData as Serializable)
       bundle.putString(SKILL_GROUP, skillGroup)
       fragment.arguments = bundle

       return fragment
   }
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
   skillData = arguments.getSerializable(
           SKILL_KEY) as SkillData
   skillGroup = arguments.getString(SKILL_GROUP)
   return inflater.inflate(R.layout.fragment_skill_details, container, false)
}

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
   setupUI()
   super.onViewCreated(view, savedInstanceState)
}

3. The setupUI() method then calls separate method for setting every part of the UI like image, name etc.

fun setupUI() {
   setImage()
   setName()
   setAuthor()
   setTryButton()
   setDescription()
   setExamples()
   setRating()
   setDynamicContent()
   setPolicy()
   setTerms()
}

4. One example of setting a part of the UI is setting Author name. It checks if AuthorName is null or not. After that it anchors author’s github account link with his/her name.

fun setAuthor() {
   skill_detail_author.text = "Author : ${activity.getString(R.string.no_skill_author)}"
   if(skillData.author != null && !skillData.author.isEmpty()){
       if(skillData.authorUrl == null || skillData.authorUrl.isEmpty())
           skill_detail_author.text = "Author : ${skillData.skillName}"
       else {
           skill_detail_author.linksClickable = true
           skill_detail_author.movementMethod = LinkMovementMethod.getInstance()
           if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
               skill_detail_author.text = Html.fromHtml("Author : <a href=\"${skillData.authorUrl}\">${skillData.author}</a>", Html.FROM_HTML_MODE_COMPACT)
           } else {
               skill_detail_author.text = Html.fromHtml("Author : <a href=\"${skillData.authorUrl}\">${skillData.author}</a>")
           }
       }
   }
}

Summary

So, this blog talked about how the Skill detail section in SUSI Android App is implemented. This included how a network call is made, logic for making different network calls, making a horizontal recyclerview for displaying examples. So, If you are looking forward to contribute to SUSI Android App, this can help you a little. But if not so, this may also help you in understanding and how you can implement horizontal recyclerview similar to Google Play Store.

References

  1. To know about servlets https://en.wikipedia.org/wiki/Java_servlet
  2. To see how to implement one https://www.javatpoint.com/servlet-tutorial
  3. To see how to make network calls in android using Retrofit https://guides.codepath.com/android/Consuming-APIs-with-Retrofit
  4. To see how to implement custom RecyclerView Adapter https://www.survivingwithandroid.com/2016/09/android-recyclerview-tutorial.html
Continue ReadingImplementing Skill Detail Section in SUSI Android App

Using Vector Images in SUSI Android

Android designed to run across many devices with different screen sizes and display resolutions. One of the things that confuse many new Android developers is how to support multiple screen sizes. For making  SUSI Android app more user-friendly and interactive we used a lot of images in the form of drawable resources. Most of these drawables are in the form of PNG (Portable Network Graphic) and to support multiple screen size we have to include separate images of varied dimensions because we can’t scale PNG images without losing quality.

Other disadvantages of using PNG images are

  • PNG images take large disk space.
  • PNG images have fixed colour and dimensions which cannot be changed.

To overcome these shortcomings of PNG image we decided to use vector images in SUSI Android.

Advantages of using vector image are

  • It is smaller in size as compared to PNG i.e it takes less disk space than PNG.
  • It can be scaled without losing quality. So we need to include only a single image for all screen size.

Disadvantages of using vector image are

  • Reconstruction of vector data may take considerably longer than that contained in a bitmap file of equivalent complexity because each image element must be drawn individually and in sequence.
  • Vector image can’t be used to display complex photographs.
  • If the object consists of a large number of small elements then file size grow very fast.

Vector produce smaller size file than bitmap only for simple stuff. When it comes to creating a photographic quality vector where colour information is paramount and may vary on a pixel-by-pixel basis, the file can be much larger than its bitmap version, that’s why vector image is only suitable for small images like logo designs, icons etc.

In this blog post, I will show you how we used vector images in SUSI Android.

How to create vector image using Asset Studio tool

Asset Studio is inbuilt Android Studio tool which we use to create a vector image from default material icon or local SVG images. To use vector asset in Android Studio

  • Go to File> New > Vector Asset
  • Vector Asset Studio Tool Window is prompted

  • Create vector image from default material icon or local SVG image.

Asset Studio will add icon in drawable. It can be used anywhere in the project.

Vector images in SUSI Android

In SUSI Android all drawable images like search, mic, arrow, check etc are vector images,  even the logo of the app is a vector image.

This vector image is the result of code you can find here.

Now I will describe some special attributes used here

  • fillColor: It defines the colour of the image. So the colour of your image depends on the colour you provide in fillColor. We used white color(#ffffff) as fillColor that’s why the image is white in colour.
  • strokeColour: It defines the colour of boundary of the image.
  • pathData: Shape of your image depends on pathData. Different commands used in pathData are
  1. M: It is used to define the starting point. For example, M10,10 means starting     Coordinate of the image will be (10,10). M used for absolute position and m   for relative position.
  2. L: L means to draw a line from the current position to given position. For example, M10,10 L20,20 means a straight line will be drawn from coordinate (10,10) to coordinate (20,20. L Use for absolute position and l for relative position.
  3. C: C means to draw an arc. For example, Cx1,x2 x3,x4 x5,x6 means draw a curve  (cubic Bezier curve) from coordinate  (x1,x2) using (x3,x4) as control point at the beginning and (x5,x6) as control point at the end.
  4. Z: Z means to close the path using the straight line between the current position and starting position.

    Example

Let’s consider a curve

M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z

It starts from point (9,16.17). From (9,16.17) to (4.83,12) a straight line will be drawn. Now again a straight will be drawn from point (4.83,12) to the relative point (-1.42,1.41). After that from point (-1.42, 1.41) it will draw a polyline like an upward arrow(^) with the end point at (21,7). Again it will draw a straight line between point (21,7) and relative point (-1.41, -1.41). At the last z will close curve using the straight line between current point and starting point).

Resources

Tags

 

Continue ReadingUsing Vector Images in SUSI Android

Implementing Change Password Feature in SUSI Android App using Custom Dialogs

Recently a new servlet was implemented on the SUSI Server about changing the password of the logged in user. This feature comes in handy to avoid unauthorized usage of the SUSI Account. Almost all the online platforms have this feature to change the password to avoid notorious user to unethical use someone else’s account. In SUSI Android app this new API was used with a nice UI to change the password of the user. The process is very simple and easy to grasp. This blog will try to cover the API information and implementation of the Change Password feature in the android client.

API Information

For changing the password of SUSI Account of the user, we have to call on  /aaa/changepassword.json

We have to provide three parameters along with this api call:

  1. changepassword:  Email of user (type string) using which user is logged in.
  2. password:  Old password (type string with min length of 6) of the user.
  3. newpassword: New password (type string with min length of 6) of the user.
  4. access_token: An encrypted access_token indicating user is logged in.

Sample Response (Success)

{
  "session": {"identity": {
    "type": "email",
    "name": "YOUR_EMAIL_ADDRESS",
    "anonymous": false
  }},
  "accepted": true,
  "message": "Your password has been changed!"
}

Error Response (Failure). This happens when user is not logged in:

HTTP ERROR 401
Problem accessing /aaa/changepassword.json. Reason:
   Base user role not sufficient. Your base user role is 'ANONYMOUS', your user role is 'anonymous'

Implementation in SUSI Android App

The change password option is located in Settings Activity and displayed only when user is logged in. So, if a logged in user wants to change the password of his/her SUSI AI account, he/she can simply go to the Settings and click on the option. Clicking on the options open up a dialog box with 3 input layouts for:

  1. Current Password
  2. New Password
  3. Confirm New Password

So, user can simply add these three inputs and click “Ok”. This will change the password of their account. Let’s see some code explanation.

  1. When user clicks on the “reset password” option from the settings, the showResetPasswordAlert() method is called which displays the dialog. And when user clicks on the “OK” button the resetPassword method() in the presenter is called passing input from the three input layout as parameters.

settingsPresenter.resetPassword(password.editText?.text.toString(), newPassword.editText?.text.toString(), conPassword.editText?.text.toString())

fun showResetPasswordAlert() {
   val builder = AlertDialog.Builder(activity)
   val resetPasswordView = activity.layoutInflater.inflate(R.layout.alert_reset_password, null)
   password = resetPasswordView.findViewById(R.id.password) as TextInputLayout
   newPassword = resetPasswordView.findViewById(R.id.newpassword) as TextInputLayout
   conPassword = resetPasswordView.findViewById(R.id.confirmpassword) as TextInputLayout
   builder.setView(resetPasswordView)
   builder.setTitle(Constant.CHANGE_PASSWORD)
           .setCancelable(false)
           .setNegativeButton(Constant.CANCEL, null)
           .setPositiveButton(getString(R.string.ok), null)
   resetPasswordAlert = builder.create()
   resetPasswordAlert.show()
   setupPasswordWatcher()
   resetPasswordAlert.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
       settingsPresenter.resetPassword(password.editText?.text.toString(), newPassword.editText?.text.toString(), conPassword.editText?.text.toString())
   }
}
  1. In the resetPassword method, all details about the passwords are checked like:
  1. If passwords are not empty.
  2. If passwords’ lengths are greater than 6.
  3. If new password and confirmation new password matches

   

When all the conditions are satisfied and all the inputs are valid, resetPassword() in model is called which makes network call to change password of the user.

settingModel.resetPassword(password,newPassword,this)

override fun resetPassword(password: String, newPassword: String, conPassword: String) {
   if (password.isEmpty()) {
       settingView?.invalidCredentials(true, Constant.PASSWORD)
       return
   }
   if (newPassword.isEmpty()) {
       settingView?.invalidCredentials(true, Constant.NEW_PASSWORD)
       return
   }
   if (conPassword.isEmpty()) {
       settingView?.invalidCredentials(true, Constant.CONFIRM_PASSWORD)
       return
   }

   if (!CredentialHelper.isPasswordValid(newPassword)) {
       settingView?.passwordInvalid(Constant.NEW_PASSWORD)
       return
   }

   if (newPassword != conPassword) {
       settingView?.invalidCredentials(false, Constant.NEW_PASSWORD)
       return
   }
   settingModel.resetPassword(password,newPassword,this)
}

Summary

So, this blog talked about how the Change Password feature is implemented in SUSI Android App. This included how a network call is made, logic for making network, information about API, making dialogs with custom UI, etc. So, If you are looking forward to contribute to SUSI Android App, this can help you a little. But if not so, this may also help you in understanding and how you can implement a dialog box with custom UI.

References

  1. To know about servlets https://en.wikipedia.org/wiki/Java_servlet
  2. To see how to implement one https://www.javatpoint.com/servlet-tutorial
  3. To see how to make network calls in android using Retrofit https://guides.codepath.com/android/Consuming-APIs-with-Retrofit
  4. Official docs for displaying dialog https://developer.android.com/guide/topics/ui/dialogs.html
  5. Implementing dialog boxes with custom UI https://stackoverflow.com/questions/13341560/how-to-create-a-custom-dialog-box-in-android
  6. Pull Request for API reference: https://github.com/fossasia/susi_server/pull/352
Continue ReadingImplementing Change Password Feature in SUSI Android App using Custom Dialogs

Full-Screen Speech Input Implementation in SUSI Android App

SUSI Android has some very good features and one of them is, it can take input in speech format from user i.e if the user says anything then it can detect it and convert it to text. This feature is implemented in SUSI Android app using Android’s built-in speech-to-text functionality. You can implement Android’s built-in speech-to-text functionality using either only RecognizerIntent class or SpeechRecognizerIntent class, RecognitionListner interface and RecognizerIntent class. Using former method has some disadvantages:

  • During speech input, it shows a dialog box (as shown here) and it breaks the connection between user and app.
  • We can’t show partial result i.e text to the user but using the later method we can show it.

We used  SpeechRecognizerIntent class, RecognitionListner interface and RecognizerIntent class to implement Android’s built-in speech-to-text functionality in SUSI Android and you know the reason for that. In this blog post, I will show you how I implemented this feature in SUSI Android with new UI.

Layout design

You can give speech input to SUSI Android either by clicking mic button

or using ‘Hi SUSI’ hotword. When you click on mic button or use ‘Hi SUSI’ hotword, you can see a screen where you will give speech input.

Two important part of this layout are:

<TextView

  android:id=“@+id/txtchat”

  android:layout_width=“wrap_content”

  android:layout_height=“wrap_content”

  

 />

  • TextView: It used to show the partial result of speech input i.e it will show converted text (partial) of your speech.
<org.fossasia.susi.ai.speechinputanimation.SpeechProgressView

  android:id=“@+id/speechprogress”

  android:layout_width=“match_parent”

  android:layout_height=“50dp”

  android:layout_margin=“8dp”

  android:layout_gravity=“center”/>

  • SpeechProgressView: It is a custom view which use to show the animation when the user gives speech input. When the user starts speaking, the animation starts. This custom view contains five bars and these five bars animate according to user input.

Full-screen speech input implementation

When the user clicks on mic button or uses ‘Hi SUSI’ hotword, a screen comes where the user can give speech input. As already mentioned I used SpeechRecognizerIntent class, RecognitionListner interface and RecognizerIntent class to implement speech-to-text functionality in SUSI Android. RecognizerIntent class starts an intent and asks for speech input

val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)

intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,

    RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)

intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, “com.domain.app”)

intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)

and send it through the speech recognizer. It does it through ACTION_RECOGNIZE_SPEECH. SpeechRecognizer class provides access to the speech recognition service. This service allows access to the speech recognizer and recognition related event occurs RecognitionListner receive notification from SpeechRecognizer class.

recognizer = SpeechRecognizer

      .createSpeechRecognizer(activity.applicationContext)

val listener = object : RecognitionListener {

 //implement all override methods

}

When the user starts speaking, the height of bars changes according to change in sound level. When sound level changes, onRmsChanged method get called where we are calling onRmsChanged method of SpeechProgressView class which is responsible for animating bars according to change in sound level.

override fun onRmsChanged(rmsdB: Float) {

  if (speechprogress != null)

      speechprogress.onRmsChanged(rmsdB)

}

When user finished speaking onEndOfSpeech method get called where we call onEndOfSpeech method of SpeechProgressView class which is responsible for rotating animation. Rotation is used to show that SUSI Android has finished listening and now it is processing your input.

override fun onEndOfSpeech() {

  if (speechprogress != null)

      speechprogress.onEndOfSpeech()

}

In case of any error, onError method get called and in case of successful speech input, onResults method get called. In both cases, we reset bars to their initial position and show chat activity user. The user can again give speech input either by clicking on mic or using ‘Hi SUSI’ hotword.

override fun onResults(results: Bundle) {

  if (speechprogress != null)

      speechprogress.onResultOrOnError()

  activity.supportFragmentManager.popBackStackImmediate()

}

Reference

Continue ReadingFull-Screen Speech Input Implementation in SUSI Android App

API to List All Users on SUSI.AI

In this blog, I discuss how the SUSI server helps in listing out all the users registered on it. The only role Susi server plays is, Whenever it receives a request at

http://api.susi.ai/aaa/getUsers.json

The server evaluate the parameters in the request, validates them and notify the user accordingly. API needs 2 parameters, out of which access-token is a necessary. 2nd parameter has to be one from the given list :

Parameter Data type

  • getPageCount boolean
  • GetUserCount boolean
  • Page integer

On the basis of this 2nd parameter, server gets to know what does the client with given access-token is requesting. Server evaluates the access-token and validates that if the access token belongs to a user with user role atleast ADMIN, then the request is valid and proceed further with fetching the data in next step. Otherwise, server responds with error code “401” and error message “Base user role not sufficient”. It is advisable for clients that before redirecting users to admin panel or any other service, Please hit

http://api.susi.ai/aaa/showAdminService.json

And check that whether the user logged in is allowed to access the admin panel or not. The servlet /showAdminService.json is quite easy to understand for even those new to programming.

Coming back to our topic, by now, server knows that this client is authorized to access the user list. But what all information does server needs to provide? In response to this request, server encodes following attributes in the JSON Array {which is part of JSON object} and sends it to user :

Attribute Description

  • Name Email-Id of the user
  • Anonymous Is this user anonymous or not
  • User Role User Role of the user
  • Confirmed User has verified account or not
  • Last Login IP Last IP from which login was requested
  • Last Login Time Time when last login request was made
  • Signup Time When did the user signed up

First things first, check if enough parameters are provided or not. If not, respond with error stating “Bad Request. No parameter present”. Otherwise, server does a general iteration which has to be done irrespective of the 2nd parameter.

First of all, get a list of all the authorized users using getAuthorizedClients method of Data Access Object class. This method picks up all the keys from authorized file {which are nothing but identification of clients from which requests are received}. Though it, skips those key which are host addresses (which can not be used to identify a user), it does includes all the email ids {which are obvious identification of users}.

public static Collection<ClientIdentity> getAuthorizedClients() {
		ArrayList<ClientIdentity> i = new ArrayList<>();
		for (String id: authorization.keys()) {
		    if(id.contains("host"))
		        continue;
			i.add(new ClientIdentity(id));
		}
		return i;
	}

In next steps, the collection is converted to suitable data types over which iterations are easy and can be converted to JSON objects and Arrays easily. After this, server evaluates which parameter is requested in the request. Let us pick each case one by one for simplicity.

  1. Client has requested number of pages in the request.

Server finds the size of keysArray {one of the object containing list of all the users}. Basic Mathematics to find out how many pages would be formed if size of each page is 50 elements and total elements are given.

if (call.get("getPageCount", false) == true) {
            int pageCount = keysArray.length % 50 == 0 ? (keysArray.length / 50) : (keysArray.length / 50) + 1;
            result.put("pageCount", pageCount);
            result.put("accepted", true);
            result.put("message", "Success: Fetched count of pages");
            return new ServiceResponse(result);
        }
  1. User count is requested

Simply return sizeof list which has list of all the users. List to be used can be anyone from authorized, keysArray or any other derivative of authorized collection. Code is quite easy.

  1.      List of users on any page is requested.

Get the page number and after applying unitary maths, you will figure out the elementary of this.

for (Client client : authorized) {
                JSONObject json = client.toJSON();
                ClientIdentity identity = new ClientIdentity(ClientIdentity.Type.email, client.getName());
                Authorization authorization = DAO.getAuthorization(identity);
                UserRole userRole = authorization.getUserRole();
                json.put("userRole", userRole.toString().toLowerCase());
                userList.add(json);
            }

If any other attribute that is required, it’s encoding will be done here.  For example, to get user role of a user, generate a client identity followed by retrieval of user role from it. Encode it and send back to user.

Other details like last login IP, last login time and signup time are also fetched from respective files.

Resources

Continue ReadingAPI to List All Users on SUSI.AI

Change Password for SUSI Accounts Using Access Token and Email-ID

In this blog, I discuss how the SUSI server synchronizes with SUSI Accounts and SUSI webchat for users to Change Password. When a user logs in, the clients store the email id of the user along with the access token in cookies. These are stored once the client gets a positive login response from the server. Both of these are required at the time of making the final call. Web clients store the email id and access token in the following way.

cookies.set('loggedIn', loggedIn, { path: '/', maxAge: time });
cookies.set('emailId', email, { path: '/', maxAge: time });

First, the client has to ask the user to enter their current password. A javascript test is used to validate that at least 6 characters must be entered by the user. A similar test is run on the new password. But while confirming the password, client checks whether the user has entered the same password as new password or not. These are just the basics. In next stage (which is achieved only when all the above conditions are met), client encodes the email id (which it gets from cookies), current password, new password and the access token (which it again extracts from cookies).

Now, Client just has to make an ajax request to the server. The response is processed and sent back to the client. Let us now look at PasswordChange Servlet.

The base user role is defined as USER. Initial steps of the servlet are to extract the values form the request it receives. The values extracted from the request are in turn used to make a client’s identity. Before that, server checks if current and new password have same values or not. If not, then server returns a JSON response to user stating, “Your current password and new password matches”. Otherwise, it will continue its control flow as it is. Look at the code snippet below:

if(password.equals(newpassword)){
            result.put("message", "Your current password and new password matches");
            result.put("accepted", false);
            return new ServiceResponse(result);
        }

The reader here may think that they have discovered a hack. But they have not. Why? Because this is just the first step. In later stages, the hash of passwords are used to match to see whether the passwords match or not. To obtain a proper client identity, first a Client credentials object is made with support from the email id which is received in ‘changepassword’ attribute. Using the ClientCredentials object made above, an object of Authentication class is made. This object uses a method defined in its class to return a valid client identity. Using the client identity, value of password hash is extracted from the database along with the salt used to hash the password. If any error is encountered while extracting the client’s password hash value and/or salt value, an error is thrown towards the client, with a message stating “invalid credentials”.

ClientCredential pwcredential = new ClientCredential(ClientCredential.Type.passwd_login, useremail);
            Authentication authentication = DAO.getAuthentication(pwcredential);
            ClientCredential emailcred = new ClientCredential(ClientCredential.Type.passwd_login,
                authentication.getIdentity().getName());
            ClientIdentity identity = authentication.getIdentity();
            String passwordHash;
            String salt;

            try {
                passwordHash = authentication.getString("passwordHash");
                salt = authentication.getString("salt");
            } catch (Throwable e) {
                Log.getLog().info("Invalid password try for user: " + identity.getName() + " from host: " + post.getClientHost() + " : password or salt missing in database");
                result.put("message", "invalid credentials");
                throw new APIException(422, "Invalid credentials");
            }

Using the same salt value that was used earlier, a hash for password entered by the user will be generated which now matches  the previous value. This is the point where the hack you were thinking you found, failed. Again the server throws an error message if user’s credential did not match. Passwords are hard to handle and easy to guess. So here we have used quite many tests before changing them. Users are not allowed to use their email id as a password as well.

If the server is clear on all the above facts and tests, It finally generates a new hashed value of the password received in the parameter ‘newpassword’ and replaces the old hash value with the new one. To notify the clients that password change exited with a success response, it sends a JSON object with message “Your password has been changed!” and accepted flag set to true.

if (DAO.hasAuthentication(emailcred)) {
                    Authentication emailauth = DAO.getAuthentication(emailcred);
                    String newsalt = createRandomString(20);
                    emailauth.remove("passwordHash");
                    emailauth.put("passwordHash", getHash(newpassword, salt));
                    Log.getLog().info("password change for user: " + identity.getName() + " via newpassword from host: " + post.getClientHost());
                    result.put("message", "Your password has been changed!");
                    result.put("accepted", true);
                }

 

Additional Resources:

Wikipedia article: What is DAO?

Continue ReadingChange Password for SUSI Accounts Using Access Token and Email-ID